diff --git a/build.gradle.kts b/build.gradle.kts index bbf26bbd84..be722c1512 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,6 @@ +import io.papermc.paperweight.util.cache +import io.papermc.paperweight.util.set + plugins { java id("com.github.johnrengelman.shadow") version "7.0.0" apply false @@ -70,3 +73,11 @@ paperweight { craftBukkitPatchPatchesDir.set(layout.projectDirectory.dir("build-data/craftbukkit-patch-patches")) } } + +val rebuildRemappedPatches by tasks.registering(io.papermc.paperweight.tasks.RebuildPaperPatches::class) { + inputDir.set(layout.cache.resolve("paperweight/patch-remap-input")) + + baseRef.set("origin/master") + + patchDir.set(layout.projectDirectory.dir("patches/server-remapped")) +} diff --git a/patches/server-unmapped/0001-POM-Changes.patch b/patches/server-unmapped-old/0001/0001-POM-Changes.patch similarity index 100% rename from patches/server-unmapped/0001-POM-Changes.patch rename to patches/server-unmapped-old/0001/0001-POM-Changes.patch diff --git a/patches/server-unmapped/0002-Paper-config-files.patch b/patches/server-unmapped-old/0001/0002-Paper-config-files.patch similarity index 100% rename from patches/server-unmapped/0002-Paper-config-files.patch rename to patches/server-unmapped-old/0001/0002-Paper-config-files.patch diff --git a/patches/server-unmapped/0003-MC-Dev-fixes.patch b/patches/server-unmapped-old/0001/0003-MC-Dev-fixes.patch similarity index 100% rename from patches/server-unmapped/0003-MC-Dev-fixes.patch rename to patches/server-unmapped-old/0001/0003-MC-Dev-fixes.patch diff --git a/patches/server-unmapped/0004-MC-Utils.patch b/patches/server-unmapped-old/0001/0004-MC-Utils.patch similarity index 100% rename from patches/server-unmapped/0004-MC-Utils.patch rename to patches/server-unmapped-old/0001/0004-MC-Utils.patch diff --git a/patches/server-unmapped/0005-Paper-Metrics.patch b/patches/server-unmapped-old/0001/0005-Paper-Metrics.patch similarity index 100% rename from patches/server-unmapped/0005-Paper-Metrics.patch rename to patches/server-unmapped-old/0001/0005-Paper-Metrics.patch diff --git a/patches/server-unmapped/0006-Add-MinecraftKey-Information-to-Objects.patch b/patches/server-unmapped-old/0001/0006-Add-MinecraftKey-Information-to-Objects.patch similarity index 100% rename from patches/server-unmapped/0006-Add-MinecraftKey-Information-to-Objects.patch rename to patches/server-unmapped-old/0001/0006-Add-MinecraftKey-Information-to-Objects.patch diff --git a/patches/server-unmapped/0007-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch b/patches/server-unmapped-old/0001/0007-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch similarity index 100% rename from patches/server-unmapped/0007-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch rename to patches/server-unmapped-old/0001/0007-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch diff --git a/patches/server-unmapped/0008-Store-counts-for-each-Entity-Block-Entity-Type.patch b/patches/server-unmapped-old/0001/0008-Store-counts-for-each-Entity-Block-Entity-Type.patch similarity index 100% rename from patches/server-unmapped/0008-Store-counts-for-each-Entity-Block-Entity-Type.patch rename to patches/server-unmapped-old/0001/0008-Store-counts-for-each-Entity-Block-Entity-Type.patch diff --git a/patches/server-unmapped/0009-Timings-v2.patch b/patches/server-unmapped-old/0001/0009-Timings-v2.patch similarity index 100% rename from patches/server-unmapped/0009-Timings-v2.patch rename to patches/server-unmapped-old/0001/0009-Timings-v2.patch diff --git a/patches/server-unmapped/0010-Adventure.patch b/patches/server-unmapped-old/0001/0010-Adventure.patch similarity index 100% rename from patches/server-unmapped/0010-Adventure.patch rename to patches/server-unmapped-old/0001/0010-Adventure.patch diff --git a/patches/server-unmapped/0011-Configurable-cactus-bamboo-and-reed-growth-heights.patch b/patches/server-unmapped-old/0001/0011-Configurable-cactus-bamboo-and-reed-growth-heights.patch similarity index 100% rename from patches/server-unmapped/0011-Configurable-cactus-bamboo-and-reed-growth-heights.patch rename to patches/server-unmapped-old/0001/0011-Configurable-cactus-bamboo-and-reed-growth-heights.patch diff --git a/patches/server-unmapped/0012-Configurable-baby-zombie-movement-speed.patch b/patches/server-unmapped-old/0001/0012-Configurable-baby-zombie-movement-speed.patch similarity index 100% rename from patches/server-unmapped/0012-Configurable-baby-zombie-movement-speed.patch rename to patches/server-unmapped-old/0001/0012-Configurable-baby-zombie-movement-speed.patch diff --git a/patches/server-unmapped/0013-Configurable-fishing-time-ranges.patch b/patches/server-unmapped-old/0001/0013-Configurable-fishing-time-ranges.patch similarity index 100% rename from patches/server-unmapped/0013-Configurable-fishing-time-ranges.patch rename to patches/server-unmapped-old/0001/0013-Configurable-fishing-time-ranges.patch diff --git a/patches/server-unmapped/0014-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch b/patches/server-unmapped-old/0001/0014-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch similarity index 100% rename from patches/server-unmapped/0014-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch rename to patches/server-unmapped-old/0001/0014-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch diff --git a/patches/server-unmapped/0015-Add-configurable-despawn-distances-for-living-entiti.patch b/patches/server-unmapped-old/0001/0015-Add-configurable-despawn-distances-for-living-entiti.patch similarity index 100% rename from patches/server-unmapped/0015-Add-configurable-despawn-distances-for-living-entiti.patch rename to patches/server-unmapped-old/0001/0015-Add-configurable-despawn-distances-for-living-entiti.patch diff --git a/patches/server-unmapped/0016-Allow-for-toggling-of-spawn-chunks.patch b/patches/server-unmapped-old/0001/0016-Allow-for-toggling-of-spawn-chunks.patch similarity index 100% rename from patches/server-unmapped/0016-Allow-for-toggling-of-spawn-chunks.patch rename to patches/server-unmapped-old/0001/0016-Allow-for-toggling-of-spawn-chunks.patch diff --git a/patches/server-unmapped/0017-Drop-falling-block-and-tnt-entities-at-the-specified.patch b/patches/server-unmapped-old/0001/0017-Drop-falling-block-and-tnt-entities-at-the-specified.patch similarity index 100% rename from patches/server-unmapped/0017-Drop-falling-block-and-tnt-entities-at-the-specified.patch rename to patches/server-unmapped-old/0001/0017-Drop-falling-block-and-tnt-entities-at-the-specified.patch diff --git a/patches/server-unmapped/0018-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch b/patches/server-unmapped-old/0001/0018-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch similarity index 100% rename from patches/server-unmapped/0018-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch rename to patches/server-unmapped-old/0001/0018-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch diff --git a/patches/server-unmapped/0019-Implement-Paper-VersionChecker.patch b/patches/server-unmapped-old/0001/0019-Implement-Paper-VersionChecker.patch similarity index 100% rename from patches/server-unmapped/0019-Implement-Paper-VersionChecker.patch rename to patches/server-unmapped-old/0001/0019-Implement-Paper-VersionChecker.patch diff --git a/patches/server-unmapped/0020-Add-version-history-to-version-command.patch b/patches/server-unmapped-old/0001/0020-Add-version-history-to-version-command.patch similarity index 100% rename from patches/server-unmapped/0020-Add-version-history-to-version-command.patch rename to patches/server-unmapped-old/0001/0020-Add-version-history-to-version-command.patch diff --git a/patches/server-unmapped/0021-Player-affects-spawning-API.patch b/patches/server-unmapped-old/0001/0021-Player-affects-spawning-API.patch similarity index 100% rename from patches/server-unmapped/0021-Player-affects-spawning-API.patch rename to patches/server-unmapped-old/0001/0021-Player-affects-spawning-API.patch diff --git a/patches/server-unmapped/0022-Remove-invalid-mob-spawner-tile-entities.patch b/patches/server-unmapped-old/0001/0022-Remove-invalid-mob-spawner-tile-entities.patch similarity index 100% rename from patches/server-unmapped/0022-Remove-invalid-mob-spawner-tile-entities.patch rename to patches/server-unmapped-old/0001/0022-Remove-invalid-mob-spawner-tile-entities.patch diff --git a/patches/server-unmapped/0023-Optimize-TileEntity-Ticking.patch b/patches/server-unmapped-old/0001/0023-Optimize-TileEntity-Ticking.patch similarity index 100% rename from patches/server-unmapped/0023-Optimize-TileEntity-Ticking.patch rename to patches/server-unmapped-old/0001/0023-Optimize-TileEntity-Ticking.patch diff --git a/patches/server-unmapped/0024-Further-improve-server-tick-loop.patch b/patches/server-unmapped-old/0001/0024-Further-improve-server-tick-loop.patch similarity index 100% rename from patches/server-unmapped/0024-Further-improve-server-tick-loop.patch rename to patches/server-unmapped-old/0001/0024-Further-improve-server-tick-loop.patch diff --git a/patches/server-unmapped/0025-Only-refresh-abilities-if-needed.patch b/patches/server-unmapped-old/0001/0025-Only-refresh-abilities-if-needed.patch similarity index 100% rename from patches/server-unmapped/0025-Only-refresh-abilities-if-needed.patch rename to patches/server-unmapped-old/0001/0025-Only-refresh-abilities-if-needed.patch diff --git a/patches/server-unmapped/0026-Entity-Origin-API.patch b/patches/server-unmapped-old/0001/0026-Entity-Origin-API.patch similarity index 100% rename from patches/server-unmapped/0026-Entity-Origin-API.patch rename to patches/server-unmapped-old/0001/0026-Entity-Origin-API.patch diff --git a/patches/server-unmapped/0027-Prevent-tile-entity-and-entity-crashes.patch b/patches/server-unmapped-old/0001/0027-Prevent-tile-entity-and-entity-crashes.patch similarity index 100% rename from patches/server-unmapped/0027-Prevent-tile-entity-and-entity-crashes.patch rename to patches/server-unmapped-old/0001/0027-Prevent-tile-entity-and-entity-crashes.patch diff --git a/patches/server-unmapped/0028-Configurable-top-of-nether-void-damage.patch b/patches/server-unmapped-old/0001/0028-Configurable-top-of-nether-void-damage.patch similarity index 100% rename from patches/server-unmapped/0028-Configurable-top-of-nether-void-damage.patch rename to patches/server-unmapped-old/0001/0028-Configurable-top-of-nether-void-damage.patch diff --git a/patches/server-unmapped/0029-Check-online-mode-before-converting-and-renaming-pla.patch b/patches/server-unmapped-old/0001/0029-Check-online-mode-before-converting-and-renaming-pla.patch similarity index 100% rename from patches/server-unmapped/0029-Check-online-mode-before-converting-and-renaming-pla.patch rename to patches/server-unmapped-old/0001/0029-Check-online-mode-before-converting-and-renaming-pla.patch diff --git a/patches/server-unmapped/0030-Always-tick-falling-blocks.patch b/patches/server-unmapped-old/0001/0030-Always-tick-falling-blocks.patch similarity index 100% rename from patches/server-unmapped/0030-Always-tick-falling-blocks.patch rename to patches/server-unmapped-old/0001/0030-Always-tick-falling-blocks.patch diff --git a/patches/server-unmapped/0031-Configurable-end-credits.patch b/patches/server-unmapped-old/0001/0031-Configurable-end-credits.patch similarity index 100% rename from patches/server-unmapped/0031-Configurable-end-credits.patch rename to patches/server-unmapped-old/0001/0031-Configurable-end-credits.patch diff --git a/patches/server-unmapped/0032-Fix-lag-from-explosions-processing-dead-entities.patch b/patches/server-unmapped-old/0001/0032-Fix-lag-from-explosions-processing-dead-entities.patch similarity index 100% rename from patches/server-unmapped/0032-Fix-lag-from-explosions-processing-dead-entities.patch rename to patches/server-unmapped-old/0001/0032-Fix-lag-from-explosions-processing-dead-entities.patch diff --git a/patches/server-unmapped/0033-Optimize-explosions.patch b/patches/server-unmapped-old/0001/0033-Optimize-explosions.patch similarity index 100% rename from patches/server-unmapped/0033-Optimize-explosions.patch rename to patches/server-unmapped-old/0001/0033-Optimize-explosions.patch diff --git a/patches/server-unmapped/0034-Disable-explosion-knockback.patch b/patches/server-unmapped-old/0001/0034-Disable-explosion-knockback.patch similarity index 100% rename from patches/server-unmapped/0034-Disable-explosion-knockback.patch rename to patches/server-unmapped-old/0001/0034-Disable-explosion-knockback.patch diff --git a/patches/server-unmapped/0035-Disable-thunder.patch b/patches/server-unmapped-old/0001/0035-Disable-thunder.patch similarity index 100% rename from patches/server-unmapped/0035-Disable-thunder.patch rename to patches/server-unmapped-old/0001/0035-Disable-thunder.patch diff --git a/patches/server-unmapped/0036-Disable-ice-and-snow.patch b/patches/server-unmapped-old/0001/0036-Disable-ice-and-snow.patch similarity index 100% rename from patches/server-unmapped/0036-Disable-ice-and-snow.patch rename to patches/server-unmapped-old/0001/0036-Disable-ice-and-snow.patch diff --git a/patches/server-unmapped/0037-Configurable-mob-spawner-tick-rate.patch b/patches/server-unmapped-old/0001/0037-Configurable-mob-spawner-tick-rate.patch similarity index 100% rename from patches/server-unmapped/0037-Configurable-mob-spawner-tick-rate.patch rename to patches/server-unmapped-old/0001/0037-Configurable-mob-spawner-tick-rate.patch diff --git a/patches/server-unmapped/0038-Send-absolute-position-the-first-time-an-entity-is-s.patch b/patches/server-unmapped-old/0001/0038-Send-absolute-position-the-first-time-an-entity-is-s.patch similarity index 100% rename from patches/server-unmapped/0038-Send-absolute-position-the-first-time-an-entity-is-s.patch rename to patches/server-unmapped-old/0001/0038-Send-absolute-position-the-first-time-an-entity-is-s.patch diff --git a/patches/server-unmapped/0039-Add-BeaconEffectEvent.patch b/patches/server-unmapped-old/0001/0039-Add-BeaconEffectEvent.patch similarity index 100% rename from patches/server-unmapped/0039-Add-BeaconEffectEvent.patch rename to patches/server-unmapped-old/0001/0039-Add-BeaconEffectEvent.patch diff --git a/patches/server-unmapped/0040-Configurable-container-update-tick-rate.patch b/patches/server-unmapped-old/0001/0040-Configurable-container-update-tick-rate.patch similarity index 100% rename from patches/server-unmapped/0040-Configurable-container-update-tick-rate.patch rename to patches/server-unmapped-old/0001/0040-Configurable-container-update-tick-rate.patch diff --git a/patches/server-unmapped/0041-Use-UserCache-for-player-heads.patch b/patches/server-unmapped-old/0001/0041-Use-UserCache-for-player-heads.patch similarity index 100% rename from patches/server-unmapped/0041-Use-UserCache-for-player-heads.patch rename to patches/server-unmapped-old/0001/0041-Use-UserCache-for-player-heads.patch diff --git a/patches/server-unmapped/0042-Disable-spigot-tick-limiters.patch b/patches/server-unmapped-old/0001/0042-Disable-spigot-tick-limiters.patch similarity index 100% rename from patches/server-unmapped/0042-Disable-spigot-tick-limiters.patch rename to patches/server-unmapped-old/0001/0042-Disable-spigot-tick-limiters.patch diff --git a/patches/server-unmapped/0043-Add-PlayerInitialSpawnEvent.patch b/patches/server-unmapped-old/0001/0043-Add-PlayerInitialSpawnEvent.patch similarity index 100% rename from patches/server-unmapped/0043-Add-PlayerInitialSpawnEvent.patch rename to patches/server-unmapped-old/0001/0043-Add-PlayerInitialSpawnEvent.patch diff --git a/patches/server-unmapped/0044-Configurable-Disabling-Cat-Chest-Detection.patch b/patches/server-unmapped-old/0001/0044-Configurable-Disabling-Cat-Chest-Detection.patch similarity index 100% rename from patches/server-unmapped/0044-Configurable-Disabling-Cat-Chest-Detection.patch rename to patches/server-unmapped-old/0001/0044-Configurable-Disabling-Cat-Chest-Detection.patch diff --git a/patches/server-unmapped/0045-Ensure-commands-are-not-ran-async.patch b/patches/server-unmapped-old/0001/0045-Ensure-commands-are-not-ran-async.patch similarity index 100% rename from patches/server-unmapped/0045-Ensure-commands-are-not-ran-async.patch rename to patches/server-unmapped-old/0001/0045-Ensure-commands-are-not-ran-async.patch diff --git a/patches/server-unmapped/0046-All-chunks-are-slime-spawn-chunks-toggle.patch b/patches/server-unmapped-old/0001/0046-All-chunks-are-slime-spawn-chunks-toggle.patch similarity index 100% rename from patches/server-unmapped/0046-All-chunks-are-slime-spawn-chunks-toggle.patch rename to patches/server-unmapped-old/0001/0046-All-chunks-are-slime-spawn-chunks-toggle.patch diff --git a/patches/server-unmapped/0047-Expose-server-CommandMap.patch b/patches/server-unmapped-old/0001/0047-Expose-server-CommandMap.patch similarity index 100% rename from patches/server-unmapped/0047-Expose-server-CommandMap.patch rename to patches/server-unmapped-old/0001/0047-Expose-server-CommandMap.patch diff --git a/patches/server-unmapped/0048-Be-a-bit-more-informative-in-maxHealth-exception.patch b/patches/server-unmapped-old/0001/0048-Be-a-bit-more-informative-in-maxHealth-exception.patch similarity index 100% rename from patches/server-unmapped/0048-Be-a-bit-more-informative-in-maxHealth-exception.patch rename to patches/server-unmapped-old/0001/0048-Be-a-bit-more-informative-in-maxHealth-exception.patch diff --git a/patches/server-unmapped/0049-Player-Tab-List-and-Title-APIs.patch b/patches/server-unmapped-old/0001/0049-Player-Tab-List-and-Title-APIs.patch similarity index 100% rename from patches/server-unmapped/0049-Player-Tab-List-and-Title-APIs.patch rename to patches/server-unmapped-old/0001/0049-Player-Tab-List-and-Title-APIs.patch diff --git a/patches/server-unmapped/0050-Ensure-inv-drag-is-in-bounds.patch b/patches/server-unmapped-old/0001/0050-Ensure-inv-drag-is-in-bounds.patch similarity index 100% rename from patches/server-unmapped/0050-Ensure-inv-drag-is-in-bounds.patch rename to patches/server-unmapped-old/0001/0050-Ensure-inv-drag-is-in-bounds.patch diff --git a/patches/server-unmapped/0051-Change-implementation-of-tile-entity-removal-list.patch b/patches/server-unmapped-old/0001/0051-Change-implementation-of-tile-entity-removal-list.patch similarity index 100% rename from patches/server-unmapped/0051-Change-implementation-of-tile-entity-removal-list.patch rename to patches/server-unmapped-old/0001/0051-Change-implementation-of-tile-entity-removal-list.patch diff --git a/patches/server-unmapped/0052-Add-configurable-portal-search-radius.patch b/patches/server-unmapped-old/0001/0052-Add-configurable-portal-search-radius.patch similarity index 100% rename from patches/server-unmapped/0052-Add-configurable-portal-search-radius.patch rename to patches/server-unmapped-old/0001/0052-Add-configurable-portal-search-radius.patch diff --git a/patches/server-unmapped/0053-Add-velocity-warnings.patch b/patches/server-unmapped-old/0001/0053-Add-velocity-warnings.patch similarity index 100% rename from patches/server-unmapped/0053-Add-velocity-warnings.patch rename to patches/server-unmapped-old/0001/0053-Add-velocity-warnings.patch diff --git a/patches/server-unmapped/0054-Configurable-inter-world-teleportation-safety.patch b/patches/server-unmapped-old/0001/0054-Configurable-inter-world-teleportation-safety.patch similarity index 100% rename from patches/server-unmapped/0054-Configurable-inter-world-teleportation-safety.patch rename to patches/server-unmapped-old/0001/0054-Configurable-inter-world-teleportation-safety.patch diff --git a/patches/server-unmapped/0055-Add-exception-reporting-event.patch b/patches/server-unmapped-old/0001/0055-Add-exception-reporting-event.patch similarity index 100% rename from patches/server-unmapped/0055-Add-exception-reporting-event.patch rename to patches/server-unmapped-old/0001/0055-Add-exception-reporting-event.patch diff --git a/patches/server-unmapped/0056-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch b/patches/server-unmapped-old/0001/0056-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch similarity index 100% rename from patches/server-unmapped/0056-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch rename to patches/server-unmapped-old/0001/0056-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch diff --git a/patches/server-unmapped/0057-Disable-Scoreboards-for-non-players-by-default.patch b/patches/server-unmapped-old/0001/0057-Disable-Scoreboards-for-non-players-by-default.patch similarity index 100% rename from patches/server-unmapped/0057-Disable-Scoreboards-for-non-players-by-default.patch rename to patches/server-unmapped-old/0001/0057-Disable-Scoreboards-for-non-players-by-default.patch diff --git a/patches/server-unmapped/0058-Add-methods-for-working-with-arrows-stuck-in-living-.patch b/patches/server-unmapped-old/0001/0058-Add-methods-for-working-with-arrows-stuck-in-living-.patch similarity index 100% rename from patches/server-unmapped/0058-Add-methods-for-working-with-arrows-stuck-in-living-.patch rename to patches/server-unmapped-old/0001/0058-Add-methods-for-working-with-arrows-stuck-in-living-.patch diff --git a/patches/server-unmapped/0059-Complete-resource-pack-API.patch b/patches/server-unmapped-old/0001/0059-Complete-resource-pack-API.patch similarity index 100% rename from patches/server-unmapped/0059-Complete-resource-pack-API.patch rename to patches/server-unmapped-old/0001/0059-Complete-resource-pack-API.patch diff --git a/patches/server-unmapped/0060-Chunk-Save-Reattempt.patch b/patches/server-unmapped-old/0001/0060-Chunk-Save-Reattempt.patch similarity index 100% rename from patches/server-unmapped/0060-Chunk-Save-Reattempt.patch rename to patches/server-unmapped-old/0001/0060-Chunk-Save-Reattempt.patch diff --git a/patches/server-unmapped/0061-Default-loading-permissions.yml-before-plugins.patch b/patches/server-unmapped-old/0001/0061-Default-loading-permissions.yml-before-plugins.patch similarity index 100% rename from patches/server-unmapped/0061-Default-loading-permissions.yml-before-plugins.patch rename to patches/server-unmapped-old/0001/0061-Default-loading-permissions.yml-before-plugins.patch diff --git a/patches/server-unmapped/0062-Allow-Reloading-of-Custom-Permissions.patch b/patches/server-unmapped-old/0001/0062-Allow-Reloading-of-Custom-Permissions.patch similarity index 100% rename from patches/server-unmapped/0062-Allow-Reloading-of-Custom-Permissions.patch rename to patches/server-unmapped-old/0001/0062-Allow-Reloading-of-Custom-Permissions.patch diff --git a/patches/server-unmapped/0063-Remove-Metadata-on-reload.patch b/patches/server-unmapped-old/0001/0063-Remove-Metadata-on-reload.patch similarity index 100% rename from patches/server-unmapped/0063-Remove-Metadata-on-reload.patch rename to patches/server-unmapped-old/0001/0063-Remove-Metadata-on-reload.patch diff --git a/patches/server-unmapped/0064-Handle-Item-Meta-Inconsistencies.patch b/patches/server-unmapped-old/0001/0064-Handle-Item-Meta-Inconsistencies.patch similarity index 100% rename from patches/server-unmapped/0064-Handle-Item-Meta-Inconsistencies.patch rename to patches/server-unmapped-old/0001/0064-Handle-Item-Meta-Inconsistencies.patch diff --git a/patches/server-unmapped/0065-Configurable-Non-Player-Arrow-Despawn-Rate.patch b/patches/server-unmapped-old/0001/0065-Configurable-Non-Player-Arrow-Despawn-Rate.patch similarity index 100% rename from patches/server-unmapped/0065-Configurable-Non-Player-Arrow-Despawn-Rate.patch rename to patches/server-unmapped-old/0001/0065-Configurable-Non-Player-Arrow-Despawn-Rate.patch diff --git a/patches/server-unmapped/0066-Add-World-Util-Methods.patch b/patches/server-unmapped-old/0001/0066-Add-World-Util-Methods.patch similarity index 100% rename from patches/server-unmapped/0066-Add-World-Util-Methods.patch rename to patches/server-unmapped-old/0001/0066-Add-World-Util-Methods.patch diff --git a/patches/server-unmapped/0067-Custom-replacement-for-eaten-items.patch b/patches/server-unmapped-old/0001/0067-Custom-replacement-for-eaten-items.patch similarity index 100% rename from patches/server-unmapped/0067-Custom-replacement-for-eaten-items.patch rename to patches/server-unmapped-old/0001/0067-Custom-replacement-for-eaten-items.patch diff --git a/patches/server-unmapped/0068-handle-NaN-health-absorb-values-and-repair-bad-data.patch b/patches/server-unmapped-old/0001/0068-handle-NaN-health-absorb-values-and-repair-bad-data.patch similarity index 100% rename from patches/server-unmapped/0068-handle-NaN-health-absorb-values-and-repair-bad-data.patch rename to patches/server-unmapped-old/0001/0068-handle-NaN-health-absorb-values-and-repair-bad-data.patch diff --git a/patches/server-unmapped/0069-Use-a-Shared-Random-for-Entities.patch b/patches/server-unmapped-old/0001/0069-Use-a-Shared-Random-for-Entities.patch similarity index 100% rename from patches/server-unmapped/0069-Use-a-Shared-Random-for-Entities.patch rename to patches/server-unmapped-old/0001/0069-Use-a-Shared-Random-for-Entities.patch diff --git a/patches/server-unmapped/0070-Configurable-spawn-chances-for-skeleton-horses.patch b/patches/server-unmapped-old/0001/0070-Configurable-spawn-chances-for-skeleton-horses.patch similarity index 100% rename from patches/server-unmapped/0070-Configurable-spawn-chances-for-skeleton-horses.patch rename to patches/server-unmapped-old/0001/0070-Configurable-spawn-chances-for-skeleton-horses.patch diff --git a/patches/server-unmapped/0071-Optimize-isValidLocation-getType-and-getBlockData-fo.patch b/patches/server-unmapped-old/0001/0071-Optimize-isValidLocation-getType-and-getBlockData-fo.patch similarity index 100% rename from patches/server-unmapped/0071-Optimize-isValidLocation-getType-and-getBlockData-fo.patch rename to patches/server-unmapped-old/0001/0071-Optimize-isValidLocation-getType-and-getBlockData-fo.patch diff --git a/patches/server-unmapped/0072-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch b/patches/server-unmapped-old/0001/0072-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch similarity index 100% rename from patches/server-unmapped/0072-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch rename to patches/server-unmapped-old/0001/0072-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch diff --git a/patches/server-unmapped/0073-Entity-AddTo-RemoveFrom-World-Events.patch b/patches/server-unmapped-old/0001/0073-Entity-AddTo-RemoveFrom-World-Events.patch similarity index 100% rename from patches/server-unmapped/0073-Entity-AddTo-RemoveFrom-World-Events.patch rename to patches/server-unmapped-old/0001/0073-Entity-AddTo-RemoveFrom-World-Events.patch diff --git a/patches/server-unmapped/0074-Configurable-Chunk-Inhabited-Time.patch b/patches/server-unmapped-old/0001/0074-Configurable-Chunk-Inhabited-Time.patch similarity index 100% rename from patches/server-unmapped/0074-Configurable-Chunk-Inhabited-Time.patch rename to patches/server-unmapped-old/0001/0074-Configurable-Chunk-Inhabited-Time.patch diff --git a/patches/server-unmapped/0075-EntityPathfindEvent.patch b/patches/server-unmapped-old/0001/0075-EntityPathfindEvent.patch similarity index 100% rename from patches/server-unmapped/0075-EntityPathfindEvent.patch rename to patches/server-unmapped-old/0001/0075-EntityPathfindEvent.patch diff --git a/patches/server-unmapped/0076-Sanitise-RegionFileCache-and-make-configurable.patch b/patches/server-unmapped-old/0001/0076-Sanitise-RegionFileCache-and-make-configurable.patch similarity index 100% rename from patches/server-unmapped/0076-Sanitise-RegionFileCache-and-make-configurable.patch rename to patches/server-unmapped-old/0001/0076-Sanitise-RegionFileCache-and-make-configurable.patch diff --git a/patches/server-unmapped/0077-Do-not-load-chunks-for-Pathfinding.patch b/patches/server-unmapped-old/0001/0077-Do-not-load-chunks-for-Pathfinding.patch similarity index 100% rename from patches/server-unmapped/0077-Do-not-load-chunks-for-Pathfinding.patch rename to patches/server-unmapped-old/0001/0077-Do-not-load-chunks-for-Pathfinding.patch diff --git a/patches/server-unmapped/0078-Add-PlayerUseUnknownEntityEvent.patch b/patches/server-unmapped-old/0001/0078-Add-PlayerUseUnknownEntityEvent.patch similarity index 100% rename from patches/server-unmapped/0078-Add-PlayerUseUnknownEntityEvent.patch rename to patches/server-unmapped-old/0001/0078-Add-PlayerUseUnknownEntityEvent.patch diff --git a/patches/server-unmapped/0079-Fix-reducedDebugInfo-not-initialized-on-client.patch b/patches/server-unmapped-old/0001/0079-Fix-reducedDebugInfo-not-initialized-on-client.patch similarity index 100% rename from patches/server-unmapped/0079-Fix-reducedDebugInfo-not-initialized-on-client.patch rename to patches/server-unmapped-old/0001/0079-Fix-reducedDebugInfo-not-initialized-on-client.patch diff --git a/patches/server-unmapped/0080-Configurable-Grass-Spread-Tick-Rate.patch b/patches/server-unmapped-old/0001/0080-Configurable-Grass-Spread-Tick-Rate.patch similarity index 100% rename from patches/server-unmapped/0080-Configurable-Grass-Spread-Tick-Rate.patch rename to patches/server-unmapped-old/0001/0080-Configurable-Grass-Spread-Tick-Rate.patch diff --git a/patches/server-unmapped/0081-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch b/patches/server-unmapped-old/0001/0081-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch similarity index 100% rename from patches/server-unmapped/0081-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch rename to patches/server-unmapped-old/0001/0081-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch diff --git a/patches/server-unmapped/0082-Optimize-DataBits.patch b/patches/server-unmapped-old/0001/0082-Optimize-DataBits.patch similarity index 100% rename from patches/server-unmapped/0082-Optimize-DataBits.patch rename to patches/server-unmapped-old/0001/0082-Optimize-DataBits.patch diff --git a/patches/server-unmapped/0083-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch b/patches/server-unmapped-old/0001/0083-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch similarity index 100% rename from patches/server-unmapped/0083-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch rename to patches/server-unmapped-old/0001/0083-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch diff --git a/patches/server-unmapped/0084-Workaround-for-setting-passengers-on-players.patch b/patches/server-unmapped-old/0001/0084-Workaround-for-setting-passengers-on-players.patch similarity index 100% rename from patches/server-unmapped/0084-Workaround-for-setting-passengers-on-players.patch rename to patches/server-unmapped-old/0001/0084-Workaround-for-setting-passengers-on-players.patch diff --git a/patches/server-unmapped/0085-Remove-unused-World-Tile-Entity-List.patch b/patches/server-unmapped-old/0001/0085-Remove-unused-World-Tile-Entity-List.patch similarity index 100% rename from patches/server-unmapped/0085-Remove-unused-World-Tile-Entity-List.patch rename to patches/server-unmapped-old/0001/0085-Remove-unused-World-Tile-Entity-List.patch diff --git a/patches/server-unmapped/0086-Don-t-tick-Skulls-unused-code.patch b/patches/server-unmapped-old/0001/0086-Don-t-tick-Skulls-unused-code.patch similarity index 100% rename from patches/server-unmapped/0086-Don-t-tick-Skulls-unused-code.patch rename to patches/server-unmapped-old/0001/0086-Don-t-tick-Skulls-unused-code.patch diff --git a/patches/server-unmapped/0087-Configurable-Player-Collision.patch b/patches/server-unmapped-old/0001/0087-Configurable-Player-Collision.patch similarity index 100% rename from patches/server-unmapped/0087-Configurable-Player-Collision.patch rename to patches/server-unmapped-old/0001/0087-Configurable-Player-Collision.patch diff --git a/patches/server-unmapped/0088-Add-handshake-event-to-allow-plugins-to-handle-clien.patch b/patches/server-unmapped-old/0001/0088-Add-handshake-event-to-allow-plugins-to-handle-clien.patch similarity index 100% rename from patches/server-unmapped/0088-Add-handshake-event-to-allow-plugins-to-handle-clien.patch rename to patches/server-unmapped-old/0001/0088-Add-handshake-event-to-allow-plugins-to-handle-clien.patch diff --git a/patches/server-unmapped/0089-Configurable-RCON-IP-address.patch b/patches/server-unmapped-old/0001/0089-Configurable-RCON-IP-address.patch similarity index 100% rename from patches/server-unmapped/0089-Configurable-RCON-IP-address.patch rename to patches/server-unmapped-old/0001/0089-Configurable-RCON-IP-address.patch diff --git a/patches/server-unmapped/0090-Prevent-Fire-from-loading-chunks-wrongly-spread.patch b/patches/server-unmapped-old/0001/0090-Prevent-Fire-from-loading-chunks-wrongly-spread.patch similarity index 100% rename from patches/server-unmapped/0090-Prevent-Fire-from-loading-chunks-wrongly-spread.patch rename to patches/server-unmapped-old/0001/0090-Prevent-Fire-from-loading-chunks-wrongly-spread.patch diff --git a/patches/server-unmapped/0091-Implement-PlayerLocaleChangeEvent.patch b/patches/server-unmapped-old/0001/0091-Implement-PlayerLocaleChangeEvent.patch similarity index 100% rename from patches/server-unmapped/0091-Implement-PlayerLocaleChangeEvent.patch rename to patches/server-unmapped-old/0001/0091-Implement-PlayerLocaleChangeEvent.patch diff --git a/patches/server-unmapped/0092-EntityRegainHealthEvent-isFastRegen-API.patch b/patches/server-unmapped-old/0001/0092-EntityRegainHealthEvent-isFastRegen-API.patch similarity index 100% rename from patches/server-unmapped/0092-EntityRegainHealthEvent-isFastRegen-API.patch rename to patches/server-unmapped-old/0001/0092-EntityRegainHealthEvent-isFastRegen-API.patch diff --git a/patches/server-unmapped/0093-Add-ability-to-configure-frosted_ice-properties.patch b/patches/server-unmapped-old/0001/0093-Add-ability-to-configure-frosted_ice-properties.patch similarity index 100% rename from patches/server-unmapped/0093-Add-ability-to-configure-frosted_ice-properties.patch rename to patches/server-unmapped-old/0001/0093-Add-ability-to-configure-frosted_ice-properties.patch diff --git a/patches/server-unmapped/0094-remove-null-possibility-for-getServer-singleton.patch b/patches/server-unmapped-old/0001/0094-remove-null-possibility-for-getServer-singleton.patch similarity index 100% rename from patches/server-unmapped/0094-remove-null-possibility-for-getServer-singleton.patch rename to patches/server-unmapped-old/0001/0094-remove-null-possibility-for-getServer-singleton.patch diff --git a/patches/server-unmapped/0095-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch b/patches/server-unmapped-old/0001/0095-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch similarity index 100% rename from patches/server-unmapped/0095-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch rename to patches/server-unmapped-old/0001/0095-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch diff --git a/patches/server-unmapped/0096-LootTable-API-Replenishable-Lootables-Feature.patch b/patches/server-unmapped-old/0001/0096-LootTable-API-Replenishable-Lootables-Feature.patch similarity index 100% rename from patches/server-unmapped/0096-LootTable-API-Replenishable-Lootables-Feature.patch rename to patches/server-unmapped-old/0001/0096-LootTable-API-Replenishable-Lootables-Feature.patch diff --git a/patches/server-unmapped/0097-Don-t-save-empty-scoreboard-teams-to-scoreboard.dat.patch b/patches/server-unmapped-old/0001/0097-Don-t-save-empty-scoreboard-teams-to-scoreboard.dat.patch similarity index 100% rename from patches/server-unmapped/0097-Don-t-save-empty-scoreboard-teams-to-scoreboard.dat.patch rename to patches/server-unmapped-old/0001/0097-Don-t-save-empty-scoreboard-teams-to-scoreboard.dat.patch diff --git a/patches/server-unmapped/0098-System-property-for-disabling-watchdoge.patch b/patches/server-unmapped-old/0001/0098-System-property-for-disabling-watchdoge.patch similarity index 100% rename from patches/server-unmapped/0098-System-property-for-disabling-watchdoge.patch rename to patches/server-unmapped-old/0001/0098-System-property-for-disabling-watchdoge.patch diff --git a/patches/server-unmapped/0099-Optimize-UserCache-Thread-Safe.patch b/patches/server-unmapped-old/0001/0099-Optimize-UserCache-Thread-Safe.patch similarity index 100% rename from patches/server-unmapped/0099-Optimize-UserCache-Thread-Safe.patch rename to patches/server-unmapped-old/0001/0099-Optimize-UserCache-Thread-Safe.patch diff --git a/patches/server-unmapped/0100-Avoid-blocking-on-Network-Manager-creation.patch b/patches/server-unmapped-old/0001/0100-Avoid-blocking-on-Network-Manager-creation.patch similarity index 100% rename from patches/server-unmapped/0100-Avoid-blocking-on-Network-Manager-creation.patch rename to patches/server-unmapped-old/0001/0100-Avoid-blocking-on-Network-Manager-creation.patch diff --git a/patches/server-unmapped/0101-Optional-TNT-doesn-t-move-in-water.patch b/patches/server-unmapped-old/0001/0101-Optional-TNT-doesn-t-move-in-water.patch similarity index 100% rename from patches/server-unmapped/0101-Optional-TNT-doesn-t-move-in-water.patch rename to patches/server-unmapped-old/0001/0101-Optional-TNT-doesn-t-move-in-water.patch diff --git a/patches/server-unmapped/0102-Faster-redstone-torch-rapid-clock-removal.patch b/patches/server-unmapped-old/0001/0102-Faster-redstone-torch-rapid-clock-removal.patch similarity index 100% rename from patches/server-unmapped/0102-Faster-redstone-torch-rapid-clock-removal.patch rename to patches/server-unmapped-old/0001/0102-Faster-redstone-torch-rapid-clock-removal.patch diff --git a/patches/server-unmapped/0103-Add-server-name-parameter.patch b/patches/server-unmapped-old/0001/0103-Add-server-name-parameter.patch similarity index 100% rename from patches/server-unmapped/0103-Add-server-name-parameter.patch rename to patches/server-unmapped-old/0001/0103-Add-server-name-parameter.patch diff --git a/patches/server-unmapped/0104-Only-send-Dragon-Wither-Death-sounds-to-same-world.patch b/patches/server-unmapped-old/0001/0104-Only-send-Dragon-Wither-Death-sounds-to-same-world.patch similarity index 100% rename from patches/server-unmapped/0104-Only-send-Dragon-Wither-Death-sounds-to-same-world.patch rename to patches/server-unmapped-old/0001/0104-Only-send-Dragon-Wither-Death-sounds-to-same-world.patch diff --git a/patches/server-unmapped/0105-Fix-Double-World-Add-issues.patch b/patches/server-unmapped-old/0001/0105-Fix-Double-World-Add-issues.patch similarity index 100% rename from patches/server-unmapped/0105-Fix-Double-World-Add-issues.patch rename to patches/server-unmapped-old/0001/0105-Fix-Double-World-Add-issues.patch diff --git a/patches/server-unmapped/0106-Fix-Old-Sign-Conversion.patch b/patches/server-unmapped-old/0001/0106-Fix-Old-Sign-Conversion.patch similarity index 100% rename from patches/server-unmapped/0106-Fix-Old-Sign-Conversion.patch rename to patches/server-unmapped-old/0001/0106-Fix-Old-Sign-Conversion.patch diff --git a/patches/server-unmapped/0107-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch b/patches/server-unmapped-old/0001/0107-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch similarity index 100% rename from patches/server-unmapped/0107-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch rename to patches/server-unmapped-old/0001/0107-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch diff --git a/patches/server-unmapped/0108-Add-setting-for-proxy-online-mode-status.patch b/patches/server-unmapped-old/0001/0108-Add-setting-for-proxy-online-mode-status.patch similarity index 100% rename from patches/server-unmapped/0108-Add-setting-for-proxy-online-mode-status.patch rename to patches/server-unmapped-old/0001/0108-Add-setting-for-proxy-online-mode-status.patch diff --git a/patches/server-unmapped/0109-Optimise-BlockState-s-hashCode-equals.patch b/patches/server-unmapped-old/0001/0109-Optimise-BlockState-s-hashCode-equals.patch similarity index 100% rename from patches/server-unmapped/0109-Optimise-BlockState-s-hashCode-equals.patch rename to patches/server-unmapped-old/0001/0109-Optimise-BlockState-s-hashCode-equals.patch diff --git a/patches/server-unmapped/0110-Configurable-packet-in-spam-threshold.patch b/patches/server-unmapped-old/0001/0110-Configurable-packet-in-spam-threshold.patch similarity index 100% rename from patches/server-unmapped/0110-Configurable-packet-in-spam-threshold.patch rename to patches/server-unmapped-old/0001/0110-Configurable-packet-in-spam-threshold.patch diff --git a/patches/server-unmapped/0111-Configurable-flying-kick-messages.patch b/patches/server-unmapped-old/0001/0111-Configurable-flying-kick-messages.patch similarity index 100% rename from patches/server-unmapped/0111-Configurable-flying-kick-messages.patch rename to patches/server-unmapped-old/0001/0111-Configurable-flying-kick-messages.patch diff --git a/patches/server-unmapped/0112-Chunk-registration-fixes.patch b/patches/server-unmapped-old/0001/0112-Chunk-registration-fixes.patch similarity index 100% rename from patches/server-unmapped/0112-Chunk-registration-fixes.patch rename to patches/server-unmapped-old/0001/0112-Chunk-registration-fixes.patch diff --git a/patches/server-unmapped/0113-Remove-FishingHook-reference-on-Craft-Entity-removal.patch b/patches/server-unmapped-old/0001/0113-Remove-FishingHook-reference-on-Craft-Entity-removal.patch similarity index 100% rename from patches/server-unmapped/0113-Remove-FishingHook-reference-on-Craft-Entity-removal.patch rename to patches/server-unmapped-old/0001/0113-Remove-FishingHook-reference-on-Craft-Entity-removal.patch diff --git a/patches/server-unmapped/0114-Auto-fix-bad-Y-levels-on-player-login.patch b/patches/server-unmapped-old/0001/0114-Auto-fix-bad-Y-levels-on-player-login.patch similarity index 100% rename from patches/server-unmapped/0114-Auto-fix-bad-Y-levels-on-player-login.patch rename to patches/server-unmapped-old/0001/0114-Auto-fix-bad-Y-levels-on-player-login.patch diff --git a/patches/server-unmapped/0115-Option-to-remove-corrupt-tile-entities.patch b/patches/server-unmapped-old/0001/0115-Option-to-remove-corrupt-tile-entities.patch similarity index 100% rename from patches/server-unmapped/0115-Option-to-remove-corrupt-tile-entities.patch rename to patches/server-unmapped-old/0001/0115-Option-to-remove-corrupt-tile-entities.patch diff --git a/patches/server-unmapped/0116-Add-EntityZapEvent.patch b/patches/server-unmapped-old/0001/0116-Add-EntityZapEvent.patch similarity index 100% rename from patches/server-unmapped/0116-Add-EntityZapEvent.patch rename to patches/server-unmapped-old/0001/0116-Add-EntityZapEvent.patch diff --git a/patches/server-unmapped/0117-Filter-bad-data-from-ArmorStand-and-SpawnEgg-items.patch b/patches/server-unmapped-old/0001/0117-Filter-bad-data-from-ArmorStand-and-SpawnEgg-items.patch similarity index 100% rename from patches/server-unmapped/0117-Filter-bad-data-from-ArmorStand-and-SpawnEgg-items.patch rename to patches/server-unmapped-old/0001/0117-Filter-bad-data-from-ArmorStand-and-SpawnEgg-items.patch diff --git a/patches/server-unmapped/0118-Cache-user-authenticator-threads.patch b/patches/server-unmapped-old/0001/0118-Cache-user-authenticator-threads.patch similarity index 100% rename from patches/server-unmapped/0118-Cache-user-authenticator-threads.patch rename to patches/server-unmapped-old/0001/0118-Cache-user-authenticator-threads.patch diff --git a/patches/server-unmapped/0119-Optimise-removeQueue.patch b/patches/server-unmapped-old/0001/0119-Optimise-removeQueue.patch similarity index 100% rename from patches/server-unmapped/0119-Optimise-removeQueue.patch rename to patches/server-unmapped-old/0001/0119-Optimise-removeQueue.patch diff --git a/patches/server-unmapped/0120-Allow-Reloading-of-Command-Aliases.patch b/patches/server-unmapped-old/0001/0120-Allow-Reloading-of-Command-Aliases.patch similarity index 100% rename from patches/server-unmapped/0120-Allow-Reloading-of-Command-Aliases.patch rename to patches/server-unmapped-old/0001/0120-Allow-Reloading-of-Command-Aliases.patch diff --git a/patches/server-unmapped/0121-Add-source-to-PlayerExpChangeEvent.patch b/patches/server-unmapped-old/0001/0121-Add-source-to-PlayerExpChangeEvent.patch similarity index 100% rename from patches/server-unmapped/0121-Add-source-to-PlayerExpChangeEvent.patch rename to patches/server-unmapped-old/0001/0121-Add-source-to-PlayerExpChangeEvent.patch diff --git a/patches/server-unmapped/0122-Don-t-let-fishinghooks-use-portals.patch b/patches/server-unmapped-old/0001/0122-Don-t-let-fishinghooks-use-portals.patch similarity index 100% rename from patches/server-unmapped/0122-Don-t-let-fishinghooks-use-portals.patch rename to patches/server-unmapped-old/0001/0122-Don-t-let-fishinghooks-use-portals.patch diff --git a/patches/server-unmapped/0123-Add-ProjectileCollideEvent.patch b/patches/server-unmapped-old/0001/0123-Add-ProjectileCollideEvent.patch similarity index 100% rename from patches/server-unmapped/0123-Add-ProjectileCollideEvent.patch rename to patches/server-unmapped-old/0001/0123-Add-ProjectileCollideEvent.patch diff --git a/patches/server-unmapped/0124-Prevent-Pathfinding-out-of-World-Border.patch b/patches/server-unmapped-old/0001/0124-Prevent-Pathfinding-out-of-World-Border.patch similarity index 100% rename from patches/server-unmapped/0124-Prevent-Pathfinding-out-of-World-Border.patch rename to patches/server-unmapped-old/0001/0124-Prevent-Pathfinding-out-of-World-Border.patch diff --git a/patches/server-unmapped/0125-Optimize-World.isLoaded-BlockPosition-Z.patch b/patches/server-unmapped-old/0001/0125-Optimize-World.isLoaded-BlockPosition-Z.patch similarity index 100% rename from patches/server-unmapped/0125-Optimize-World.isLoaded-BlockPosition-Z.patch rename to patches/server-unmapped-old/0001/0125-Optimize-World.isLoaded-BlockPosition-Z.patch diff --git a/patches/server-unmapped/0126-Bound-Treasure-Maps-to-World-Border.patch b/patches/server-unmapped-old/0001/0126-Bound-Treasure-Maps-to-World-Border.patch similarity index 100% rename from patches/server-unmapped/0126-Bound-Treasure-Maps-to-World-Border.patch rename to patches/server-unmapped-old/0001/0126-Bound-Treasure-Maps-to-World-Border.patch diff --git a/patches/server-unmapped/0127-Configurable-Cartographer-Treasure-Maps.patch b/patches/server-unmapped-old/0001/0127-Configurable-Cartographer-Treasure-Maps.patch similarity index 100% rename from patches/server-unmapped/0127-Configurable-Cartographer-Treasure-Maps.patch rename to patches/server-unmapped-old/0001/0127-Configurable-Cartographer-Treasure-Maps.patch diff --git a/patches/server-unmapped/0128-Optimize-ItemStack.isEmpty.patch b/patches/server-unmapped-old/0001/0128-Optimize-ItemStack.isEmpty.patch similarity index 100% rename from patches/server-unmapped/0128-Optimize-ItemStack.isEmpty.patch rename to patches/server-unmapped-old/0001/0128-Optimize-ItemStack.isEmpty.patch diff --git a/patches/server-unmapped/0129-Add-API-methods-to-control-if-armour-stands-can-move.patch b/patches/server-unmapped-old/0001/0129-Add-API-methods-to-control-if-armour-stands-can-move.patch similarity index 100% rename from patches/server-unmapped/0129-Add-API-methods-to-control-if-armour-stands-can-move.patch rename to patches/server-unmapped-old/0001/0129-Add-API-methods-to-control-if-armour-stands-can-move.patch diff --git a/patches/server-unmapped/0130-Properly-fix-item-duplication-bug.patch b/patches/server-unmapped-old/0001/0130-Properly-fix-item-duplication-bug.patch similarity index 100% rename from patches/server-unmapped/0130-Properly-fix-item-duplication-bug.patch rename to patches/server-unmapped-old/0001/0130-Properly-fix-item-duplication-bug.patch diff --git a/patches/server-unmapped/0131-String-based-Action-Bar-API.patch b/patches/server-unmapped-old/0001/0131-String-based-Action-Bar-API.patch similarity index 100% rename from patches/server-unmapped/0131-String-based-Action-Bar-API.patch rename to patches/server-unmapped-old/0001/0131-String-based-Action-Bar-API.patch diff --git a/patches/server-unmapped/0132-Firework-API-s.patch b/patches/server-unmapped-old/0001/0132-Firework-API-s.patch similarity index 100% rename from patches/server-unmapped/0132-Firework-API-s.patch rename to patches/server-unmapped-old/0001/0132-Firework-API-s.patch diff --git a/patches/server-unmapped/0133-PlayerTeleportEndGatewayEvent.patch b/patches/server-unmapped-old/0001/0133-PlayerTeleportEndGatewayEvent.patch similarity index 100% rename from patches/server-unmapped/0133-PlayerTeleportEndGatewayEvent.patch rename to patches/server-unmapped-old/0001/0133-PlayerTeleportEndGatewayEvent.patch diff --git a/patches/server-unmapped/0134-Provide-E-TE-Chunk-count-stat-methods.patch b/patches/server-unmapped-old/0001/0134-Provide-E-TE-Chunk-count-stat-methods.patch similarity index 100% rename from patches/server-unmapped/0134-Provide-E-TE-Chunk-count-stat-methods.patch rename to patches/server-unmapped-old/0001/0134-Provide-E-TE-Chunk-count-stat-methods.patch diff --git a/patches/server-unmapped/0135-Enforce-Sync-Player-Saves.patch b/patches/server-unmapped-old/0001/0135-Enforce-Sync-Player-Saves.patch similarity index 100% rename from patches/server-unmapped/0135-Enforce-Sync-Player-Saves.patch rename to patches/server-unmapped-old/0001/0135-Enforce-Sync-Player-Saves.patch diff --git a/patches/server-unmapped/0136-Don-t-allow-entities-to-ride-themselves-572.patch b/patches/server-unmapped-old/0001/0136-Don-t-allow-entities-to-ride-themselves-572.patch similarity index 100% rename from patches/server-unmapped/0136-Don-t-allow-entities-to-ride-themselves-572.patch rename to patches/server-unmapped-old/0001/0136-Don-t-allow-entities-to-ride-themselves-572.patch diff --git a/patches/server-unmapped/0137-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch b/patches/server-unmapped-old/0001/0137-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch similarity index 100% rename from patches/server-unmapped/0137-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch rename to patches/server-unmapped-old/0001/0137-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch diff --git a/patches/server-unmapped/0138-Cap-Entity-Collisions.patch b/patches/server-unmapped-old/0001/0138-Cap-Entity-Collisions.patch similarity index 100% rename from patches/server-unmapped/0138-Cap-Entity-Collisions.patch rename to patches/server-unmapped-old/0001/0138-Cap-Entity-Collisions.patch diff --git a/patches/server-unmapped/0139-Remove-CraftScheduler-Async-Task-Debugger.patch b/patches/server-unmapped-old/0001/0139-Remove-CraftScheduler-Async-Task-Debugger.patch similarity index 100% rename from patches/server-unmapped/0139-Remove-CraftScheduler-Async-Task-Debugger.patch rename to patches/server-unmapped-old/0001/0139-Remove-CraftScheduler-Async-Task-Debugger.patch diff --git a/patches/server-unmapped/0140-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch b/patches/server-unmapped-old/0001/0140-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch similarity index 100% rename from patches/server-unmapped/0140-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch rename to patches/server-unmapped-old/0001/0140-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch diff --git a/patches/server-unmapped/0141-Do-not-let-armorstands-drown.patch b/patches/server-unmapped-old/0001/0141-Do-not-let-armorstands-drown.patch similarity index 100% rename from patches/server-unmapped/0141-Do-not-let-armorstands-drown.patch rename to patches/server-unmapped-old/0001/0141-Do-not-let-armorstands-drown.patch diff --git a/patches/server-unmapped/0142-Properly-handle-async-calls-to-restart-the-server.patch b/patches/server-unmapped-old/0001/0142-Properly-handle-async-calls-to-restart-the-server.patch similarity index 100% rename from patches/server-unmapped/0142-Properly-handle-async-calls-to-restart-the-server.patch rename to patches/server-unmapped-old/0001/0142-Properly-handle-async-calls-to-restart-the-server.patch diff --git a/patches/server-unmapped/0143-Add-system-property-to-disable-book-size-limits.patch b/patches/server-unmapped-old/0001/0143-Add-system-property-to-disable-book-size-limits.patch similarity index 100% rename from patches/server-unmapped/0143-Add-system-property-to-disable-book-size-limits.patch rename to patches/server-unmapped-old/0001/0143-Add-system-property-to-disable-book-size-limits.patch diff --git a/patches/server-unmapped/0144-Add-option-to-make-parrots-stay-on-shoulders-despite.patch b/patches/server-unmapped-old/0001/0144-Add-option-to-make-parrots-stay-on-shoulders-despite.patch similarity index 100% rename from patches/server-unmapped/0144-Add-option-to-make-parrots-stay-on-shoulders-despite.patch rename to patches/server-unmapped-old/0001/0144-Add-option-to-make-parrots-stay-on-shoulders-despite.patch diff --git a/patches/server-unmapped/0145-Add-configuration-option-to-prevent-player-names-fro.patch b/patches/server-unmapped-old/0001/0145-Add-configuration-option-to-prevent-player-names-fro.patch similarity index 100% rename from patches/server-unmapped/0145-Add-configuration-option-to-prevent-player-names-fro.patch rename to patches/server-unmapped-old/0001/0145-Add-configuration-option-to-prevent-player-names-fro.patch diff --git a/patches/server-unmapped/0146-Use-TerminalConsoleAppender-for-console-improvements.patch b/patches/server-unmapped-old/0001/0146-Use-TerminalConsoleAppender-for-console-improvements.patch similarity index 100% rename from patches/server-unmapped/0146-Use-TerminalConsoleAppender-for-console-improvements.patch rename to patches/server-unmapped-old/0001/0146-Use-TerminalConsoleAppender-for-console-improvements.patch diff --git a/patches/server-unmapped/0147-provide-a-configurable-option-to-disable-creeper-lin.patch b/patches/server-unmapped-old/0001/0147-provide-a-configurable-option-to-disable-creeper-lin.patch similarity index 100% rename from patches/server-unmapped/0147-provide-a-configurable-option-to-disable-creeper-lin.patch rename to patches/server-unmapped-old/0001/0147-provide-a-configurable-option-to-disable-creeper-lin.patch diff --git a/patches/server-unmapped/0148-Item-canEntityPickup.patch b/patches/server-unmapped-old/0001/0148-Item-canEntityPickup.patch similarity index 100% rename from patches/server-unmapped/0148-Item-canEntityPickup.patch rename to patches/server-unmapped-old/0001/0148-Item-canEntityPickup.patch diff --git a/patches/server-unmapped/0149-PlayerPickupItemEvent-setFlyAtPlayer.patch b/patches/server-unmapped-old/0001/0149-PlayerPickupItemEvent-setFlyAtPlayer.patch similarity index 100% rename from patches/server-unmapped/0149-PlayerPickupItemEvent-setFlyAtPlayer.patch rename to patches/server-unmapped-old/0001/0149-PlayerPickupItemEvent-setFlyAtPlayer.patch diff --git a/patches/server-unmapped/0150-PlayerAttemptPickupItemEvent.patch b/patches/server-unmapped-old/0001/0150-PlayerAttemptPickupItemEvent.patch similarity index 100% rename from patches/server-unmapped/0150-PlayerAttemptPickupItemEvent.patch rename to patches/server-unmapped-old/0001/0150-PlayerAttemptPickupItemEvent.patch diff --git a/patches/server-unmapped/0151-Add-UnknownCommandEvent.patch b/patches/server-unmapped-old/0001/0151-Add-UnknownCommandEvent.patch similarity index 100% rename from patches/server-unmapped/0151-Add-UnknownCommandEvent.patch rename to patches/server-unmapped-old/0001/0151-Add-UnknownCommandEvent.patch diff --git a/patches/server-unmapped/0152-Basic-PlayerProfile-API.patch b/patches/server-unmapped-old/0001/0152-Basic-PlayerProfile-API.patch similarity index 100% rename from patches/server-unmapped/0152-Basic-PlayerProfile-API.patch rename to patches/server-unmapped-old/0001/0152-Basic-PlayerProfile-API.patch diff --git a/patches/server-unmapped/0153-Shoulder-Entities-Release-API.patch b/patches/server-unmapped-old/0001/0153-Shoulder-Entities-Release-API.patch similarity index 100% rename from patches/server-unmapped/0153-Shoulder-Entities-Release-API.patch rename to patches/server-unmapped-old/0001/0153-Shoulder-Entities-Release-API.patch diff --git a/patches/server-unmapped/0154-Profile-Lookup-Events.patch b/patches/server-unmapped-old/0001/0154-Profile-Lookup-Events.patch similarity index 100% rename from patches/server-unmapped/0154-Profile-Lookup-Events.patch rename to patches/server-unmapped-old/0001/0154-Profile-Lookup-Events.patch diff --git a/patches/server-unmapped/0155-Block-player-logins-during-server-shutdown.patch b/patches/server-unmapped-old/0001/0155-Block-player-logins-during-server-shutdown.patch similarity index 100% rename from patches/server-unmapped/0155-Block-player-logins-during-server-shutdown.patch rename to patches/server-unmapped-old/0001/0155-Block-player-logins-during-server-shutdown.patch diff --git a/patches/server-unmapped/0156-Entity-fromMobSpawner.patch b/patches/server-unmapped-old/0001/0156-Entity-fromMobSpawner.patch similarity index 100% rename from patches/server-unmapped/0156-Entity-fromMobSpawner.patch rename to patches/server-unmapped-old/0001/0156-Entity-fromMobSpawner.patch diff --git a/patches/server-unmapped/0157-Improve-the-Saddle-API-for-Horses.patch b/patches/server-unmapped-old/0001/0157-Improve-the-Saddle-API-for-Horses.patch similarity index 100% rename from patches/server-unmapped/0157-Improve-the-Saddle-API-for-Horses.patch rename to patches/server-unmapped-old/0001/0157-Improve-the-Saddle-API-for-Horses.patch diff --git a/patches/server-unmapped/0158-Implement-ensureServerConversions-API.patch b/patches/server-unmapped-old/0001/0158-Implement-ensureServerConversions-API.patch similarity index 100% rename from patches/server-unmapped/0158-Implement-ensureServerConversions-API.patch rename to patches/server-unmapped-old/0001/0158-Implement-ensureServerConversions-API.patch diff --git a/patches/server-unmapped/0159-Implement-getI18NDisplayName.patch b/patches/server-unmapped-old/0001/0159-Implement-getI18NDisplayName.patch similarity index 100% rename from patches/server-unmapped/0159-Implement-getI18NDisplayName.patch rename to patches/server-unmapped-old/0001/0159-Implement-getI18NDisplayName.patch diff --git a/patches/server-unmapped/0160-ProfileWhitelistVerifyEvent.patch b/patches/server-unmapped-old/0001/0160-ProfileWhitelistVerifyEvent.patch similarity index 100% rename from patches/server-unmapped/0160-ProfileWhitelistVerifyEvent.patch rename to patches/server-unmapped-old/0001/0160-ProfileWhitelistVerifyEvent.patch diff --git a/patches/server-unmapped/0161-Fix-this-stupid-bullshit.patch b/patches/server-unmapped-old/0001/0161-Fix-this-stupid-bullshit.patch similarity index 100% rename from patches/server-unmapped/0161-Fix-this-stupid-bullshit.patch rename to patches/server-unmapped-old/0001/0161-Fix-this-stupid-bullshit.patch diff --git a/patches/server-unmapped/0162-Ocelot-despawns-should-honor-nametags-and-leash.patch b/patches/server-unmapped-old/0001/0162-Ocelot-despawns-should-honor-nametags-and-leash.patch similarity index 100% rename from patches/server-unmapped/0162-Ocelot-despawns-should-honor-nametags-and-leash.patch rename to patches/server-unmapped-old/0001/0162-Ocelot-despawns-should-honor-nametags-and-leash.patch diff --git a/patches/server-unmapped/0163-Reset-spawner-timer-when-spawner-event-is-cancelled.patch b/patches/server-unmapped-old/0001/0163-Reset-spawner-timer-when-spawner-event-is-cancelled.patch similarity index 100% rename from patches/server-unmapped/0163-Reset-spawner-timer-when-spawner-event-is-cancelled.patch rename to patches/server-unmapped-old/0001/0163-Reset-spawner-timer-when-spawner-event-is-cancelled.patch diff --git a/patches/server-unmapped/0164-Fix-MC-117075-TE-Unload-Lag-Spike.patch b/patches/server-unmapped-old/0001/0164-Fix-MC-117075-TE-Unload-Lag-Spike.patch similarity index 100% rename from patches/server-unmapped/0164-Fix-MC-117075-TE-Unload-Lag-Spike.patch rename to patches/server-unmapped-old/0001/0164-Fix-MC-117075-TE-Unload-Lag-Spike.patch diff --git a/patches/server-unmapped/0165-Allow-specifying-a-custom-authentication-servers-dow.patch b/patches/server-unmapped-old/0001/0165-Allow-specifying-a-custom-authentication-servers-dow.patch similarity index 100% rename from patches/server-unmapped/0165-Allow-specifying-a-custom-authentication-servers-dow.patch rename to patches/server-unmapped-old/0001/0165-Allow-specifying-a-custom-authentication-servers-dow.patch diff --git a/patches/server-unmapped/0166-LivingEntity-setKiller.patch b/patches/server-unmapped-old/0001/0166-LivingEntity-setKiller.patch similarity index 100% rename from patches/server-unmapped/0166-LivingEntity-setKiller.patch rename to patches/server-unmapped-old/0001/0166-LivingEntity-setKiller.patch diff --git a/patches/server-unmapped/0167-Handle-plugin-prefixes-using-Log4J-configuration.patch b/patches/server-unmapped-old/0001/0167-Handle-plugin-prefixes-using-Log4J-configuration.patch similarity index 100% rename from patches/server-unmapped/0167-Handle-plugin-prefixes-using-Log4J-configuration.patch rename to patches/server-unmapped-old/0001/0167-Handle-plugin-prefixes-using-Log4J-configuration.patch diff --git a/patches/server-unmapped/0168-Include-Log4J2-SLF4J-implementation.patch b/patches/server-unmapped-old/0001/0168-Include-Log4J2-SLF4J-implementation.patch similarity index 100% rename from patches/server-unmapped/0168-Include-Log4J2-SLF4J-implementation.patch rename to patches/server-unmapped-old/0001/0168-Include-Log4J2-SLF4J-implementation.patch diff --git a/patches/server-unmapped/0169-Improve-Log4J-Configuration-Plugin-Loggers.patch b/patches/server-unmapped-old/0001/0169-Improve-Log4J-Configuration-Plugin-Loggers.patch similarity index 100% rename from patches/server-unmapped/0169-Improve-Log4J-Configuration-Plugin-Loggers.patch rename to patches/server-unmapped-old/0001/0169-Improve-Log4J-Configuration-Plugin-Loggers.patch diff --git a/patches/server-unmapped/0170-Add-PlayerJumpEvent.patch b/patches/server-unmapped-old/0001/0170-Add-PlayerJumpEvent.patch similarity index 100% rename from patches/server-unmapped/0170-Add-PlayerJumpEvent.patch rename to patches/server-unmapped-old/0001/0170-Add-PlayerJumpEvent.patch diff --git a/patches/server-unmapped/0171-handle-PacketPlayInKeepAlive-async.patch b/patches/server-unmapped-old/0001/0171-handle-PacketPlayInKeepAlive-async.patch similarity index 100% rename from patches/server-unmapped/0171-handle-PacketPlayInKeepAlive-async.patch rename to patches/server-unmapped-old/0001/0171-handle-PacketPlayInKeepAlive-async.patch diff --git a/patches/server-unmapped/0172-Expose-client-protocol-version-and-virtual-host.patch b/patches/server-unmapped-old/0001/0172-Expose-client-protocol-version-and-virtual-host.patch similarity index 100% rename from patches/server-unmapped/0172-Expose-client-protocol-version-and-virtual-host.patch rename to patches/server-unmapped-old/0001/0172-Expose-client-protocol-version-and-virtual-host.patch diff --git a/patches/server-unmapped/0173-revert-serverside-behavior-of-keepalives.patch b/patches/server-unmapped-old/0001/0173-revert-serverside-behavior-of-keepalives.patch similarity index 100% rename from patches/server-unmapped/0173-revert-serverside-behavior-of-keepalives.patch rename to patches/server-unmapped-old/0001/0173-revert-serverside-behavior-of-keepalives.patch diff --git a/patches/server-unmapped/0174-Send-attack-SoundEffects-only-to-players-who-can-see.patch b/patches/server-unmapped-old/0001/0174-Send-attack-SoundEffects-only-to-players-who-can-see.patch similarity index 100% rename from patches/server-unmapped/0174-Send-attack-SoundEffects-only-to-players-who-can-see.patch rename to patches/server-unmapped-old/0001/0174-Send-attack-SoundEffects-only-to-players-who-can-see.patch diff --git a/patches/server-unmapped/0175-Option-for-maximum-exp-value-when-merging-orbs.patch b/patches/server-unmapped-old/0001/0175-Option-for-maximum-exp-value-when-merging-orbs.patch similarity index 100% rename from patches/server-unmapped/0175-Option-for-maximum-exp-value-when-merging-orbs.patch rename to patches/server-unmapped-old/0001/0175-Option-for-maximum-exp-value-when-merging-orbs.patch diff --git a/patches/server-unmapped/0176-Add-PlayerArmorChangeEvent.patch b/patches/server-unmapped-old/0001/0176-Add-PlayerArmorChangeEvent.patch similarity index 100% rename from patches/server-unmapped/0176-Add-PlayerArmorChangeEvent.patch rename to patches/server-unmapped-old/0001/0176-Add-PlayerArmorChangeEvent.patch diff --git a/patches/server-unmapped/0177-Prevent-logins-from-being-processed-when-the-player-.patch b/patches/server-unmapped-old/0001/0177-Prevent-logins-from-being-processed-when-the-player-.patch similarity index 100% rename from patches/server-unmapped/0177-Prevent-logins-from-being-processed-when-the-player-.patch rename to patches/server-unmapped-old/0001/0177-Prevent-logins-from-being-processed-when-the-player-.patch diff --git a/patches/server-unmapped/0178-use-CB-BlockState-implementations-for-captured-block.patch b/patches/server-unmapped-old/0001/0178-use-CB-BlockState-implementations-for-captured-block.patch similarity index 100% rename from patches/server-unmapped/0178-use-CB-BlockState-implementations-for-captured-block.patch rename to patches/server-unmapped-old/0001/0178-use-CB-BlockState-implementations-for-captured-block.patch diff --git a/patches/server-unmapped/0179-API-to-get-a-BlockState-without-a-snapshot.patch b/patches/server-unmapped-old/0001/0179-API-to-get-a-BlockState-without-a-snapshot.patch similarity index 100% rename from patches/server-unmapped/0179-API-to-get-a-BlockState-without-a-snapshot.patch rename to patches/server-unmapped-old/0001/0179-API-to-get-a-BlockState-without-a-snapshot.patch diff --git a/patches/server-unmapped/0180-AsyncTabCompleteEvent.patch b/patches/server-unmapped-old/0001/0180-AsyncTabCompleteEvent.patch similarity index 100% rename from patches/server-unmapped/0180-AsyncTabCompleteEvent.patch rename to patches/server-unmapped-old/0001/0180-AsyncTabCompleteEvent.patch diff --git a/patches/server-unmapped/0181-Avoid-NPE-in-PathfinderGoalTempt.patch b/patches/server-unmapped-old/0001/0181-Avoid-NPE-in-PathfinderGoalTempt.patch similarity index 100% rename from patches/server-unmapped/0181-Avoid-NPE-in-PathfinderGoalTempt.patch rename to patches/server-unmapped-old/0001/0181-Avoid-NPE-in-PathfinderGoalTempt.patch diff --git a/patches/server-unmapped/0182-PlayerPickupExperienceEvent.patch b/patches/server-unmapped-old/0001/0182-PlayerPickupExperienceEvent.patch similarity index 100% rename from patches/server-unmapped/0182-PlayerPickupExperienceEvent.patch rename to patches/server-unmapped-old/0001/0182-PlayerPickupExperienceEvent.patch diff --git a/patches/server-unmapped/0183-ExperienceOrbMergeEvent.patch b/patches/server-unmapped-old/0001/0183-ExperienceOrbMergeEvent.patch similarity index 100% rename from patches/server-unmapped/0183-ExperienceOrbMergeEvent.patch rename to patches/server-unmapped-old/0001/0183-ExperienceOrbMergeEvent.patch diff --git a/patches/server-unmapped/0184-Ability-to-apply-mending-to-XP-API.patch b/patches/server-unmapped-old/0001/0184-Ability-to-apply-mending-to-XP-API.patch similarity index 100% rename from patches/server-unmapped/0184-Ability-to-apply-mending-to-XP-API.patch rename to patches/server-unmapped-old/0001/0184-Ability-to-apply-mending-to-XP-API.patch diff --git a/patches/server-unmapped/0185-Make-max-squid-spawn-height-configurable.patch b/patches/server-unmapped-old/0001/0185-Make-max-squid-spawn-height-configurable.patch similarity index 100% rename from patches/server-unmapped/0185-Make-max-squid-spawn-height-configurable.patch rename to patches/server-unmapped-old/0001/0185-Make-max-squid-spawn-height-configurable.patch diff --git a/patches/server-unmapped/0186-PreCreatureSpawnEvent.patch b/patches/server-unmapped-old/0001/0186-PreCreatureSpawnEvent.patch similarity index 100% rename from patches/server-unmapped/0186-PreCreatureSpawnEvent.patch rename to patches/server-unmapped-old/0001/0186-PreCreatureSpawnEvent.patch diff --git a/patches/server-unmapped/0187-PlayerNaturallySpawnCreaturesEvent.patch b/patches/server-unmapped-old/0001/0187-PlayerNaturallySpawnCreaturesEvent.patch similarity index 100% rename from patches/server-unmapped/0187-PlayerNaturallySpawnCreaturesEvent.patch rename to patches/server-unmapped-old/0001/0187-PlayerNaturallySpawnCreaturesEvent.patch diff --git a/patches/server-unmapped/0188-Add-setPlayerProfile-API-for-Skulls.patch b/patches/server-unmapped-old/0001/0188-Add-setPlayerProfile-API-for-Skulls.patch similarity index 100% rename from patches/server-unmapped/0188-Add-setPlayerProfile-API-for-Skulls.patch rename to patches/server-unmapped-old/0001/0188-Add-setPlayerProfile-API-for-Skulls.patch diff --git a/patches/server-unmapped/0189-Fill-Profile-Property-Events.patch b/patches/server-unmapped-old/0001/0189-Fill-Profile-Property-Events.patch similarity index 100% rename from patches/server-unmapped/0189-Fill-Profile-Property-Events.patch rename to patches/server-unmapped-old/0001/0189-Fill-Profile-Property-Events.patch diff --git a/patches/server-unmapped/0190-PlayerAdvancementCriterionGrantEvent.patch b/patches/server-unmapped-old/0001/0190-PlayerAdvancementCriterionGrantEvent.patch similarity index 100% rename from patches/server-unmapped/0190-PlayerAdvancementCriterionGrantEvent.patch rename to patches/server-unmapped-old/0001/0190-PlayerAdvancementCriterionGrantEvent.patch diff --git a/patches/server-unmapped/0191-Add-ArmorStand-Item-Meta.patch b/patches/server-unmapped-old/0001/0191-Add-ArmorStand-Item-Meta.patch similarity index 100% rename from patches/server-unmapped/0191-Add-ArmorStand-Item-Meta.patch rename to patches/server-unmapped-old/0001/0191-Add-ArmorStand-Item-Meta.patch diff --git a/patches/server-unmapped/0192-Extend-Player-Interact-cancellation.patch b/patches/server-unmapped-old/0001/0192-Extend-Player-Interact-cancellation.patch similarity index 100% rename from patches/server-unmapped/0192-Extend-Player-Interact-cancellation.patch rename to patches/server-unmapped-old/0001/0192-Extend-Player-Interact-cancellation.patch diff --git a/patches/server-unmapped/0193-Tameable-getOwnerUniqueId-API.patch b/patches/server-unmapped-old/0001/0193-Tameable-getOwnerUniqueId-API.patch similarity index 100% rename from patches/server-unmapped/0193-Tameable-getOwnerUniqueId-API.patch rename to patches/server-unmapped-old/0001/0193-Tameable-getOwnerUniqueId-API.patch diff --git a/patches/server-unmapped/0194-Toggleable-player-crits-helps-mitigate-hacked-client.patch b/patches/server-unmapped-old/0001/0194-Toggleable-player-crits-helps-mitigate-hacked-client.patch similarity index 100% rename from patches/server-unmapped/0194-Toggleable-player-crits-helps-mitigate-hacked-client.patch rename to patches/server-unmapped-old/0001/0194-Toggleable-player-crits-helps-mitigate-hacked-client.patch diff --git a/patches/server-unmapped/0195-Prevent-Frosted-Ice-from-loading-holding-chunks.patch b/patches/server-unmapped-old/0001/0195-Prevent-Frosted-Ice-from-loading-holding-chunks.patch similarity index 100% rename from patches/server-unmapped/0195-Prevent-Frosted-Ice-from-loading-holding-chunks.patch rename to patches/server-unmapped-old/0001/0195-Prevent-Frosted-Ice-from-loading-holding-chunks.patch diff --git a/patches/server-unmapped/0196-Disable-Explicit-Network-Manager-Flushing.patch b/patches/server-unmapped-old/0001/0196-Disable-Explicit-Network-Manager-Flushing.patch similarity index 100% rename from patches/server-unmapped/0196-Disable-Explicit-Network-Manager-Flushing.patch rename to patches/server-unmapped-old/0001/0196-Disable-Explicit-Network-Manager-Flushing.patch diff --git a/patches/server-unmapped/0197-Implement-extended-PaperServerListPingEvent.patch b/patches/server-unmapped-old/0001/0197-Implement-extended-PaperServerListPingEvent.patch similarity index 100% rename from patches/server-unmapped/0197-Implement-extended-PaperServerListPingEvent.patch rename to patches/server-unmapped-old/0001/0197-Implement-extended-PaperServerListPingEvent.patch diff --git a/patches/server-unmapped/0198-Improved-Async-Task-Scheduler.patch b/patches/server-unmapped-old/0001/0198-Improved-Async-Task-Scheduler.patch similarity index 100% rename from patches/server-unmapped/0198-Improved-Async-Task-Scheduler.patch rename to patches/server-unmapped-old/0001/0198-Improved-Async-Task-Scheduler.patch diff --git a/patches/server-unmapped/0199-Ability-to-change-PlayerProfile-in-AsyncPreLoginEven.patch b/patches/server-unmapped-old/0001/0199-Ability-to-change-PlayerProfile-in-AsyncPreLoginEven.patch similarity index 100% rename from patches/server-unmapped/0199-Ability-to-change-PlayerProfile-in-AsyncPreLoginEven.patch rename to patches/server-unmapped-old/0001/0199-Ability-to-change-PlayerProfile-in-AsyncPreLoginEven.patch diff --git a/patches/server-unmapped/0200-Player.setPlayerProfile-API.patch b/patches/server-unmapped-old/0001/0200-Player.setPlayerProfile-API.patch similarity index 100% rename from patches/server-unmapped/0200-Player.setPlayerProfile-API.patch rename to patches/server-unmapped-old/0001/0200-Player.setPlayerProfile-API.patch diff --git a/patches/server-unmapped/0201-Fix-Dragon-Server-Crashes.patch b/patches/server-unmapped-old/0001/0201-Fix-Dragon-Server-Crashes.patch similarity index 100% rename from patches/server-unmapped/0201-Fix-Dragon-Server-Crashes.patch rename to patches/server-unmapped-old/0001/0201-Fix-Dragon-Server-Crashes.patch diff --git a/patches/server-unmapped/0202-getPlayerUniqueId-API.patch b/patches/server-unmapped-old/0001/0202-getPlayerUniqueId-API.patch similarity index 100% rename from patches/server-unmapped/0202-getPlayerUniqueId-API.patch rename to patches/server-unmapped-old/0001/0202-getPlayerUniqueId-API.patch diff --git a/patches/server-unmapped/0203-Make-player-data-saving-configurable.patch b/patches/server-unmapped-old/0001/0203-Make-player-data-saving-configurable.patch similarity index 100% rename from patches/server-unmapped/0203-Make-player-data-saving-configurable.patch rename to patches/server-unmapped-old/0001/0203-Make-player-data-saving-configurable.patch diff --git a/patches/server-unmapped/0204-Make-legacy-ping-handler-more-reliable.patch b/patches/server-unmapped-old/0001/0204-Make-legacy-ping-handler-more-reliable.patch similarity index 100% rename from patches/server-unmapped/0204-Make-legacy-ping-handler-more-reliable.patch rename to patches/server-unmapped-old/0001/0204-Make-legacy-ping-handler-more-reliable.patch diff --git a/patches/server-unmapped/0205-Call-PaperServerListPingEvent-for-legacy-pings.patch b/patches/server-unmapped-old/0001/0205-Call-PaperServerListPingEvent-for-legacy-pings.patch similarity index 100% rename from patches/server-unmapped/0205-Call-PaperServerListPingEvent-for-legacy-pings.patch rename to patches/server-unmapped-old/0001/0205-Call-PaperServerListPingEvent-for-legacy-pings.patch diff --git a/patches/server-unmapped/0206-Flag-to-disable-the-channel-limit.patch b/patches/server-unmapped-old/0001/0206-Flag-to-disable-the-channel-limit.patch similarity index 100% rename from patches/server-unmapped/0206-Flag-to-disable-the-channel-limit.patch rename to patches/server-unmapped-old/0001/0206-Flag-to-disable-the-channel-limit.patch diff --git a/patches/server-unmapped/0207-Add-method-to-open-already-placed-sign.patch b/patches/server-unmapped-old/0001/0207-Add-method-to-open-already-placed-sign.patch similarity index 100% rename from patches/server-unmapped/0207-Add-method-to-open-already-placed-sign.patch rename to patches/server-unmapped-old/0001/0207-Add-method-to-open-already-placed-sign.patch diff --git a/patches/server-unmapped/0208-Configurable-sprint-interruption-on-attack.patch b/patches/server-unmapped-old/0001/0208-Configurable-sprint-interruption-on-attack.patch similarity index 100% rename from patches/server-unmapped/0208-Configurable-sprint-interruption-on-attack.patch rename to patches/server-unmapped-old/0001/0208-Configurable-sprint-interruption-on-attack.patch diff --git a/patches/server-unmapped/0209-Fix-exploit-that-allowed-colored-signs-to-be-created.patch b/patches/server-unmapped-old/0001/0209-Fix-exploit-that-allowed-colored-signs-to-be-created.patch similarity index 100% rename from patches/server-unmapped/0209-Fix-exploit-that-allowed-colored-signs-to-be-created.patch rename to patches/server-unmapped-old/0001/0209-Fix-exploit-that-allowed-colored-signs-to-be-created.patch diff --git a/patches/server-unmapped/0210-EndermanEscapeEvent.patch b/patches/server-unmapped-old/0001/0210-EndermanEscapeEvent.patch similarity index 100% rename from patches/server-unmapped/0210-EndermanEscapeEvent.patch rename to patches/server-unmapped-old/0001/0210-EndermanEscapeEvent.patch diff --git a/patches/server-unmapped/0211-Enderman.teleportRandomly.patch b/patches/server-unmapped-old/0001/0211-Enderman.teleportRandomly.patch similarity index 100% rename from patches/server-unmapped/0211-Enderman.teleportRandomly.patch rename to patches/server-unmapped-old/0001/0211-Enderman.teleportRandomly.patch diff --git a/patches/server-unmapped/0212-Block-Enderpearl-Travel-Exploit.patch b/patches/server-unmapped-old/0001/0212-Block-Enderpearl-Travel-Exploit.patch similarity index 100% rename from patches/server-unmapped/0212-Block-Enderpearl-Travel-Exploit.patch rename to patches/server-unmapped-old/0001/0212-Block-Enderpearl-Travel-Exploit.patch diff --git a/patches/server-unmapped/0213-Expand-World.spawnParticle-API-and-add-Builder.patch b/patches/server-unmapped-old/0001/0213-Expand-World.spawnParticle-API-and-add-Builder.patch similarity index 100% rename from patches/server-unmapped/0213-Expand-World.spawnParticle-API-and-add-Builder.patch rename to patches/server-unmapped-old/0001/0213-Expand-World.spawnParticle-API-and-add-Builder.patch diff --git a/patches/server-unmapped/0214-EndermanAttackPlayerEvent.patch b/patches/server-unmapped-old/0001/0214-EndermanAttackPlayerEvent.patch similarity index 100% rename from patches/server-unmapped/0214-EndermanAttackPlayerEvent.patch rename to patches/server-unmapped-old/0001/0214-EndermanAttackPlayerEvent.patch diff --git a/patches/server-unmapped/0215-WitchConsumePotionEvent.patch b/patches/server-unmapped-old/0001/0215-WitchConsumePotionEvent.patch similarity index 100% rename from patches/server-unmapped/0215-WitchConsumePotionEvent.patch rename to patches/server-unmapped-old/0001/0215-WitchConsumePotionEvent.patch diff --git a/patches/server-unmapped/0216-WitchThrowPotionEvent.patch b/patches/server-unmapped-old/0001/0216-WitchThrowPotionEvent.patch similarity index 100% rename from patches/server-unmapped/0216-WitchThrowPotionEvent.patch rename to patches/server-unmapped-old/0001/0216-WitchThrowPotionEvent.patch diff --git a/patches/server-unmapped/0217-Allow-spawning-Item-entities-with-World.spawnEntity.patch b/patches/server-unmapped-old/0001/0217-Allow-spawning-Item-entities-with-World.spawnEntity.patch similarity index 100% rename from patches/server-unmapped/0217-Allow-spawning-Item-entities-with-World.spawnEntity.patch rename to patches/server-unmapped-old/0001/0217-Allow-spawning-Item-entities-with-World.spawnEntity.patch diff --git a/patches/server-unmapped/0218-WitchReadyPotionEvent.patch b/patches/server-unmapped-old/0001/0218-WitchReadyPotionEvent.patch similarity index 100% rename from patches/server-unmapped/0218-WitchReadyPotionEvent.patch rename to patches/server-unmapped-old/0001/0218-WitchReadyPotionEvent.patch diff --git a/patches/server-unmapped/0219-ItemStack-getMaxItemUseDuration.patch b/patches/server-unmapped-old/0001/0219-ItemStack-getMaxItemUseDuration.patch similarity index 100% rename from patches/server-unmapped/0219-ItemStack-getMaxItemUseDuration.patch rename to patches/server-unmapped-old/0001/0219-ItemStack-getMaxItemUseDuration.patch diff --git a/patches/server-unmapped/0220-Implement-EntityTeleportEndGatewayEvent.patch b/patches/server-unmapped-old/0001/0220-Implement-EntityTeleportEndGatewayEvent.patch similarity index 100% rename from patches/server-unmapped/0220-Implement-EntityTeleportEndGatewayEvent.patch rename to patches/server-unmapped-old/0001/0220-Implement-EntityTeleportEndGatewayEvent.patch diff --git a/patches/server-unmapped/0221-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch b/patches/server-unmapped-old/0001/0221-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch similarity index 100% rename from patches/server-unmapped/0221-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch rename to patches/server-unmapped-old/0001/0221-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch diff --git a/patches/server-unmapped/0222-Fix-CraftEntity-hashCode.patch b/patches/server-unmapped-old/0001/0222-Fix-CraftEntity-hashCode.patch similarity index 100% rename from patches/server-unmapped/0222-Fix-CraftEntity-hashCode.patch rename to patches/server-unmapped-old/0001/0222-Fix-CraftEntity-hashCode.patch diff --git a/patches/server-unmapped/0223-Configurable-Alternative-LootPool-Luck-Formula.patch b/patches/server-unmapped-old/0001/0223-Configurable-Alternative-LootPool-Luck-Formula.patch similarity index 100% rename from patches/server-unmapped/0223-Configurable-Alternative-LootPool-Luck-Formula.patch rename to patches/server-unmapped-old/0001/0223-Configurable-Alternative-LootPool-Luck-Formula.patch diff --git a/patches/server-unmapped/0224-Print-Error-details-when-failing-to-save-player-data.patch b/patches/server-unmapped-old/0001/0224-Print-Error-details-when-failing-to-save-player-data.patch similarity index 100% rename from patches/server-unmapped/0224-Print-Error-details-when-failing-to-save-player-data.patch rename to patches/server-unmapped-old/0001/0224-Print-Error-details-when-failing-to-save-player-data.patch diff --git a/patches/server-unmapped/0225-Make-shield-blocking-delay-configurable.patch b/patches/server-unmapped-old/0001/0225-Make-shield-blocking-delay-configurable.patch similarity index 100% rename from patches/server-unmapped/0225-Make-shield-blocking-delay-configurable.patch rename to patches/server-unmapped-old/0001/0225-Make-shield-blocking-delay-configurable.patch diff --git a/patches/server-unmapped/0226-Improve-EntityShootBowEvent.patch b/patches/server-unmapped-old/0001/0226-Improve-EntityShootBowEvent.patch similarity index 100% rename from patches/server-unmapped/0226-Improve-EntityShootBowEvent.patch rename to patches/server-unmapped-old/0001/0226-Improve-EntityShootBowEvent.patch diff --git a/patches/server-unmapped/0227-PlayerReadyArrowEvent.patch b/patches/server-unmapped-old/0001/0227-PlayerReadyArrowEvent.patch similarity index 100% rename from patches/server-unmapped/0227-PlayerReadyArrowEvent.patch rename to patches/server-unmapped-old/0001/0227-PlayerReadyArrowEvent.patch diff --git a/patches/server-unmapped/0228-Implement-EntityKnockbackByEntityEvent.patch b/patches/server-unmapped-old/0001/0228-Implement-EntityKnockbackByEntityEvent.patch similarity index 100% rename from patches/server-unmapped/0228-Implement-EntityKnockbackByEntityEvent.patch rename to patches/server-unmapped-old/0001/0228-Implement-EntityKnockbackByEntityEvent.patch diff --git a/patches/server-unmapped/0229-Expand-Explosions-API.patch b/patches/server-unmapped-old/0001/0229-Expand-Explosions-API.patch similarity index 100% rename from patches/server-unmapped/0229-Expand-Explosions-API.patch rename to patches/server-unmapped-old/0001/0229-Expand-Explosions-API.patch diff --git a/patches/server-unmapped/0230-LivingEntity-Hand-Raised-Item-Use-API.patch b/patches/server-unmapped-old/0001/0230-LivingEntity-Hand-Raised-Item-Use-API.patch similarity index 100% rename from patches/server-unmapped/0230-LivingEntity-Hand-Raised-Item-Use-API.patch rename to patches/server-unmapped-old/0001/0230-LivingEntity-Hand-Raised-Item-Use-API.patch diff --git a/patches/server-unmapped/0231-RangedEntity-API.patch b/patches/server-unmapped-old/0001/0231-RangedEntity-API.patch similarity index 100% rename from patches/server-unmapped/0231-RangedEntity-API.patch rename to patches/server-unmapped-old/0001/0231-RangedEntity-API.patch diff --git a/patches/server-unmapped/0232-Add-config-to-disable-ender-dragon-legacy-check.patch b/patches/server-unmapped-old/0001/0232-Add-config-to-disable-ender-dragon-legacy-check.patch similarity index 100% rename from patches/server-unmapped/0232-Add-config-to-disable-ender-dragon-legacy-check.patch rename to patches/server-unmapped-old/0001/0232-Add-config-to-disable-ender-dragon-legacy-check.patch diff --git a/patches/server-unmapped/0233-Implement-World.getEntity-UUID-API.patch b/patches/server-unmapped-old/0001/0233-Implement-World.getEntity-UUID-API.patch similarity index 100% rename from patches/server-unmapped/0233-Implement-World.getEntity-UUID-API.patch rename to patches/server-unmapped-old/0001/0233-Implement-World.getEntity-UUID-API.patch diff --git a/patches/server-unmapped/0234-InventoryCloseEvent-Reason-API.patch b/patches/server-unmapped-old/0001/0234-InventoryCloseEvent-Reason-API.patch similarity index 100% rename from patches/server-unmapped/0234-InventoryCloseEvent-Reason-API.patch rename to patches/server-unmapped-old/0001/0234-InventoryCloseEvent-Reason-API.patch diff --git a/patches/server-unmapped/0235-Vex-getSummoner-API.patch b/patches/server-unmapped-old/0001/0235-Vex-getSummoner-API.patch similarity index 100% rename from patches/server-unmapped/0235-Vex-getSummoner-API.patch rename to patches/server-unmapped-old/0001/0235-Vex-getSummoner-API.patch diff --git a/patches/server-unmapped/0236-Refresh-player-inventory-when-cancelling-PlayerInter.patch b/patches/server-unmapped-old/0001/0236-Refresh-player-inventory-when-cancelling-PlayerInter.patch similarity index 100% rename from patches/server-unmapped/0236-Refresh-player-inventory-when-cancelling-PlayerInter.patch rename to patches/server-unmapped-old/0001/0236-Refresh-player-inventory-when-cancelling-PlayerInter.patch diff --git a/patches/server-unmapped/0237-Don-t-change-the-Entity-Random-seed-for-squids.patch b/patches/server-unmapped-old/0001/0237-Don-t-change-the-Entity-Random-seed-for-squids.patch similarity index 100% rename from patches/server-unmapped/0237-Don-t-change-the-Entity-Random-seed-for-squids.patch rename to patches/server-unmapped-old/0001/0237-Don-t-change-the-Entity-Random-seed-for-squids.patch diff --git a/patches/server-unmapped/0238-Re-add-vanilla-entity-warnings-for-duplicates.patch b/patches/server-unmapped-old/0001/0238-Re-add-vanilla-entity-warnings-for-duplicates.patch similarity index 100% rename from patches/server-unmapped/0238-Re-add-vanilla-entity-warnings-for-duplicates.patch rename to patches/server-unmapped-old/0001/0238-Re-add-vanilla-entity-warnings-for-duplicates.patch diff --git a/patches/server-unmapped/0239-Avoid-item-merge-if-stack-size-above-max-stack-size.patch b/patches/server-unmapped-old/0001/0239-Avoid-item-merge-if-stack-size-above-max-stack-size.patch similarity index 100% rename from patches/server-unmapped/0239-Avoid-item-merge-if-stack-size-above-max-stack-size.patch rename to patches/server-unmapped-old/0001/0239-Avoid-item-merge-if-stack-size-above-max-stack-size.patch diff --git a/patches/server-unmapped/0240-Use-asynchronous-Log4j-2-loggers.patch b/patches/server-unmapped-old/0001/0240-Use-asynchronous-Log4j-2-loggers.patch similarity index 100% rename from patches/server-unmapped/0240-Use-asynchronous-Log4j-2-loggers.patch rename to patches/server-unmapped-old/0001/0240-Use-asynchronous-Log4j-2-loggers.patch diff --git a/patches/server-unmapped/0241-add-more-information-to-Entity.toString.patch b/patches/server-unmapped-old/0001/0241-add-more-information-to-Entity.toString.patch similarity index 100% rename from patches/server-unmapped/0241-add-more-information-to-Entity.toString.patch rename to patches/server-unmapped-old/0001/0241-add-more-information-to-Entity.toString.patch diff --git a/patches/server-unmapped/0242-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch b/patches/server-unmapped-old/0001/0242-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch similarity index 100% rename from patches/server-unmapped/0242-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch rename to patches/server-unmapped-old/0001/0242-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch diff --git a/patches/server-unmapped/0243-EnderDragon-Events.patch b/patches/server-unmapped-old/0001/0243-EnderDragon-Events.patch similarity index 100% rename from patches/server-unmapped/0243-EnderDragon-Events.patch rename to patches/server-unmapped-old/0001/0243-EnderDragon-Events.patch diff --git a/patches/server-unmapped/0244-PlayerElytraBoostEvent.patch b/patches/server-unmapped-old/0001/0244-PlayerElytraBoostEvent.patch similarity index 100% rename from patches/server-unmapped/0244-PlayerElytraBoostEvent.patch rename to patches/server-unmapped-old/0001/0244-PlayerElytraBoostEvent.patch diff --git a/patches/server-unmapped/0245-Improve-BlockPosition-inlining.patch b/patches/server-unmapped-old/0001/0245-Improve-BlockPosition-inlining.patch similarity index 100% rename from patches/server-unmapped/0245-Improve-BlockPosition-inlining.patch rename to patches/server-unmapped-old/0001/0245-Improve-BlockPosition-inlining.patch diff --git a/patches/server-unmapped/0246-Optimize-RegistryID.c.patch b/patches/server-unmapped-old/0001/0246-Optimize-RegistryID.c.patch similarity index 100% rename from patches/server-unmapped/0246-Optimize-RegistryID.c.patch rename to patches/server-unmapped-old/0001/0246-Optimize-RegistryID.c.patch diff --git a/patches/server-unmapped/0247-Option-to-prevent-armor-stands-from-doing-entity-loo.patch b/patches/server-unmapped-old/0001/0247-Option-to-prevent-armor-stands-from-doing-entity-loo.patch similarity index 100% rename from patches/server-unmapped/0247-Option-to-prevent-armor-stands-from-doing-entity-loo.patch rename to patches/server-unmapped-old/0001/0247-Option-to-prevent-armor-stands-from-doing-entity-loo.patch diff --git a/patches/server-unmapped/0248-Vanished-players-don-t-have-rights.patch b/patches/server-unmapped-old/0001/0248-Vanished-players-don-t-have-rights.patch similarity index 100% rename from patches/server-unmapped/0248-Vanished-players-don-t-have-rights.patch rename to patches/server-unmapped-old/0001/0248-Vanished-players-don-t-have-rights.patch diff --git a/patches/server-unmapped/0249-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch b/patches/server-unmapped-old/0001/0249-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch similarity index 100% rename from patches/server-unmapped/0249-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch rename to patches/server-unmapped-old/0001/0249-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch diff --git a/patches/server-unmapped/0250-Add-some-Debug-to-Chunk-Entity-slices.patch b/patches/server-unmapped-old/0001/0250-Add-some-Debug-to-Chunk-Entity-slices.patch similarity index 100% rename from patches/server-unmapped/0250-Add-some-Debug-to-Chunk-Entity-slices.patch rename to patches/server-unmapped-old/0001/0250-Add-some-Debug-to-Chunk-Entity-slices.patch diff --git a/patches/server-unmapped/0251-SkeletonHorse-Additions.patch b/patches/server-unmapped-old/0001/0251-SkeletonHorse-Additions.patch similarity index 100% rename from patches/server-unmapped/0251-SkeletonHorse-Additions.patch rename to patches/server-unmapped-old/0001/0251-SkeletonHorse-Additions.patch diff --git a/patches/server-unmapped/0252-Prevent-Saving-Bad-entities-to-chunks.patch b/patches/server-unmapped-old/0001/0252-Prevent-Saving-Bad-entities-to-chunks.patch similarity index 100% rename from patches/server-unmapped/0252-Prevent-Saving-Bad-entities-to-chunks.patch rename to patches/server-unmapped-old/0001/0252-Prevent-Saving-Bad-entities-to-chunks.patch diff --git a/patches/server-unmapped/0253-Don-t-call-getItemMeta-on-hasItemMeta.patch b/patches/server-unmapped-old/0001/0253-Don-t-call-getItemMeta-on-hasItemMeta.patch similarity index 100% rename from patches/server-unmapped/0253-Don-t-call-getItemMeta-on-hasItemMeta.patch rename to patches/server-unmapped-old/0001/0253-Don-t-call-getItemMeta-on-hasItemMeta.patch diff --git a/patches/server-unmapped/0254-Ignore-Dead-Entities-in-entityList-iteration.patch b/patches/server-unmapped-old/0001/0254-Ignore-Dead-Entities-in-entityList-iteration.patch similarity index 100% rename from patches/server-unmapped/0254-Ignore-Dead-Entities-in-entityList-iteration.patch rename to patches/server-unmapped-old/0001/0254-Ignore-Dead-Entities-in-entityList-iteration.patch diff --git a/patches/server-unmapped/0255-Implement-Expanded-ArmorStand-API.patch b/patches/server-unmapped-old/0001/0255-Implement-Expanded-ArmorStand-API.patch similarity index 100% rename from patches/server-unmapped/0255-Implement-Expanded-ArmorStand-API.patch rename to patches/server-unmapped-old/0001/0255-Implement-Expanded-ArmorStand-API.patch diff --git a/patches/server-unmapped/0256-AnvilDamageEvent.patch b/patches/server-unmapped-old/0001/0256-AnvilDamageEvent.patch similarity index 100% rename from patches/server-unmapped/0256-AnvilDamageEvent.patch rename to patches/server-unmapped-old/0001/0256-AnvilDamageEvent.patch diff --git a/patches/server-unmapped/0257-Add-TNTPrimeEvent.patch b/patches/server-unmapped-old/0001/0257-Add-TNTPrimeEvent.patch similarity index 100% rename from patches/server-unmapped/0257-Add-TNTPrimeEvent.patch rename to patches/server-unmapped-old/0001/0257-Add-TNTPrimeEvent.patch diff --git a/patches/server-unmapped/0258-Break-up-and-make-tab-spam-limits-configurable.patch b/patches/server-unmapped-old/0001/0258-Break-up-and-make-tab-spam-limits-configurable.patch similarity index 100% rename from patches/server-unmapped/0258-Break-up-and-make-tab-spam-limits-configurable.patch rename to patches/server-unmapped-old/0001/0258-Break-up-and-make-tab-spam-limits-configurable.patch diff --git a/patches/server-unmapped/0259-Add-hand-to-bucket-events.patch b/patches/server-unmapped-old/0001/0259-Add-hand-to-bucket-events.patch similarity index 100% rename from patches/server-unmapped/0259-Add-hand-to-bucket-events.patch rename to patches/server-unmapped-old/0001/0259-Add-hand-to-bucket-events.patch diff --git a/patches/server-unmapped/0260-MC-135506-Experience-should-save-as-Integers.patch b/patches/server-unmapped-old/0001/0260-MC-135506-Experience-should-save-as-Integers.patch similarity index 100% rename from patches/server-unmapped/0260-MC-135506-Experience-should-save-as-Integers.patch rename to patches/server-unmapped-old/0001/0260-MC-135506-Experience-should-save-as-Integers.patch diff --git a/patches/server-unmapped/0261-Fix-client-rendering-skulls-from-same-user.patch b/patches/server-unmapped-old/0001/0261-Fix-client-rendering-skulls-from-same-user.patch similarity index 100% rename from patches/server-unmapped/0261-Fix-client-rendering-skulls-from-same-user.patch rename to patches/server-unmapped-old/0001/0261-Fix-client-rendering-skulls-from-same-user.patch diff --git a/patches/server-unmapped/0262-Add-Early-Warning-Feature-to-WatchDog.patch b/patches/server-unmapped-old/0001/0262-Add-Early-Warning-Feature-to-WatchDog.patch similarity index 100% rename from patches/server-unmapped/0262-Add-Early-Warning-Feature-to-WatchDog.patch rename to patches/server-unmapped-old/0001/0262-Add-Early-Warning-Feature-to-WatchDog.patch diff --git a/patches/server-unmapped/0263-Make-EnderDragon-implement-Mob.patch b/patches/server-unmapped-old/0001/0263-Make-EnderDragon-implement-Mob.patch similarity index 100% rename from patches/server-unmapped/0263-Make-EnderDragon-implement-Mob.patch rename to patches/server-unmapped-old/0001/0263-Make-EnderDragon-implement-Mob.patch diff --git a/patches/server-unmapped/0264-Use-ConcurrentHashMap-in-JsonList.patch b/patches/server-unmapped-old/0001/0264-Use-ConcurrentHashMap-in-JsonList.patch similarity index 100% rename from patches/server-unmapped/0264-Use-ConcurrentHashMap-in-JsonList.patch rename to patches/server-unmapped-old/0001/0264-Use-ConcurrentHashMap-in-JsonList.patch diff --git a/patches/server-unmapped/0265-Use-a-Queue-for-Queueing-Commands.patch b/patches/server-unmapped-old/0001/0265-Use-a-Queue-for-Queueing-Commands.patch similarity index 100% rename from patches/server-unmapped/0265-Use-a-Queue-for-Queueing-Commands.patch rename to patches/server-unmapped-old/0001/0265-Use-a-Queue-for-Queueing-Commands.patch diff --git a/patches/server-unmapped/0266-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch b/patches/server-unmapped-old/0001/0266-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch similarity index 100% rename from patches/server-unmapped/0266-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch rename to patches/server-unmapped-old/0001/0266-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch diff --git a/patches/server-unmapped/0267-Allow-disabling-armour-stand-ticking.patch b/patches/server-unmapped-old/0001/0267-Allow-disabling-armour-stand-ticking.patch similarity index 100% rename from patches/server-unmapped/0267-Allow-disabling-armour-stand-ticking.patch rename to patches/server-unmapped-old/0001/0267-Allow-disabling-armour-stand-ticking.patch diff --git a/patches/server-unmapped/0268-Optimize-BlockPosition-helper-methods.patch b/patches/server-unmapped-old/0001/0268-Optimize-BlockPosition-helper-methods.patch similarity index 100% rename from patches/server-unmapped/0268-Optimize-BlockPosition-helper-methods.patch rename to patches/server-unmapped-old/0001/0268-Optimize-BlockPosition-helper-methods.patch diff --git a/patches/server-unmapped/0269-Restore-vanlla-default-mob-spawn-range.patch b/patches/server-unmapped-old/0001/0269-Restore-vanlla-default-mob-spawn-range.patch similarity index 100% rename from patches/server-unmapped/0269-Restore-vanlla-default-mob-spawn-range.patch rename to patches/server-unmapped-old/0001/0269-Restore-vanlla-default-mob-spawn-range.patch diff --git a/patches/server-unmapped/0270-Slime-Pathfinder-Events.patch b/patches/server-unmapped-old/0001/0270-Slime-Pathfinder-Events.patch similarity index 100% rename from patches/server-unmapped/0270-Slime-Pathfinder-Events.patch rename to patches/server-unmapped-old/0001/0270-Slime-Pathfinder-Events.patch diff --git a/patches/server-unmapped/0271-Configurable-speed-for-water-flowing-over-lava.patch b/patches/server-unmapped-old/0001/0271-Configurable-speed-for-water-flowing-over-lava.patch similarity index 100% rename from patches/server-unmapped/0271-Configurable-speed-for-water-flowing-over-lava.patch rename to patches/server-unmapped-old/0001/0271-Configurable-speed-for-water-flowing-over-lava.patch diff --git a/patches/server-unmapped/0272-Optimize-CraftBlockData-Creation.patch b/patches/server-unmapped-old/0001/0272-Optimize-CraftBlockData-Creation.patch similarity index 100% rename from patches/server-unmapped/0272-Optimize-CraftBlockData-Creation.patch rename to patches/server-unmapped-old/0001/0272-Optimize-CraftBlockData-Creation.patch diff --git a/patches/server-unmapped/0273-Optimize-RegistryMaterials.patch b/patches/server-unmapped-old/0001/0273-Optimize-RegistryMaterials.patch similarity index 100% rename from patches/server-unmapped/0273-Optimize-RegistryMaterials.patch rename to patches/server-unmapped-old/0001/0273-Optimize-RegistryMaterials.patch diff --git a/patches/server-unmapped/0274-Add-PhantomPreSpawnEvent.patch b/patches/server-unmapped-old/0001/0274-Add-PhantomPreSpawnEvent.patch similarity index 100% rename from patches/server-unmapped/0274-Add-PhantomPreSpawnEvent.patch rename to patches/server-unmapped-old/0001/0274-Add-PhantomPreSpawnEvent.patch diff --git a/patches/server-unmapped/0275-Add-More-Creeper-API.patch b/patches/server-unmapped-old/0001/0275-Add-More-Creeper-API.patch similarity index 100% rename from patches/server-unmapped/0275-Add-More-Creeper-API.patch rename to patches/server-unmapped-old/0001/0275-Add-More-Creeper-API.patch diff --git a/patches/server-unmapped/0276-Inventory-removeItemAnySlot.patch b/patches/server-unmapped-old/0001/0276-Inventory-removeItemAnySlot.patch similarity index 100% rename from patches/server-unmapped/0276-Inventory-removeItemAnySlot.patch rename to patches/server-unmapped-old/0001/0276-Inventory-removeItemAnySlot.patch diff --git a/patches/server-unmapped/0277-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch b/patches/server-unmapped-old/0001/0277-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch similarity index 100% rename from patches/server-unmapped/0277-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch rename to patches/server-unmapped-old/0001/0277-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch diff --git a/patches/server-unmapped/0278-Add-ray-tracing-methods-to-LivingEntity.patch b/patches/server-unmapped-old/0001/0278-Add-ray-tracing-methods-to-LivingEntity.patch similarity index 100% rename from patches/server-unmapped/0278-Add-ray-tracing-methods-to-LivingEntity.patch rename to patches/server-unmapped-old/0001/0278-Add-ray-tracing-methods-to-LivingEntity.patch diff --git a/patches/server-unmapped/0279-Expose-attack-cooldown-methods-for-Player.patch b/patches/server-unmapped-old/0001/0279-Expose-attack-cooldown-methods-for-Player.patch similarity index 100% rename from patches/server-unmapped/0279-Expose-attack-cooldown-methods-for-Player.patch rename to patches/server-unmapped-old/0001/0279-Expose-attack-cooldown-methods-for-Player.patch diff --git a/patches/server-unmapped/0280-Improve-death-events.patch b/patches/server-unmapped-old/0001/0280-Improve-death-events.patch similarity index 100% rename from patches/server-unmapped/0280-Improve-death-events.patch rename to patches/server-unmapped-old/0001/0280-Improve-death-events.patch diff --git a/patches/server-unmapped/0281-Allow-chests-to-be-placed-with-NBT-data.patch b/patches/server-unmapped-old/0001/0281-Allow-chests-to-be-placed-with-NBT-data.patch similarity index 100% rename from patches/server-unmapped/0281-Allow-chests-to-be-placed-with-NBT-data.patch rename to patches/server-unmapped-old/0001/0281-Allow-chests-to-be-placed-with-NBT-data.patch diff --git a/patches/server-unmapped/0282-Mob-Pathfinding-API.patch b/patches/server-unmapped-old/0001/0282-Mob-Pathfinding-API.patch similarity index 100% rename from patches/server-unmapped/0282-Mob-Pathfinding-API.patch rename to patches/server-unmapped-old/0001/0282-Mob-Pathfinding-API.patch diff --git a/patches/server-unmapped/0283-Prevent-chunk-loading-from-Fluid-Flowing.patch b/patches/server-unmapped-old/0001/0283-Prevent-chunk-loading-from-Fluid-Flowing.patch similarity index 100% rename from patches/server-unmapped/0283-Prevent-chunk-loading-from-Fluid-Flowing.patch rename to patches/server-unmapped-old/0001/0283-Prevent-chunk-loading-from-Fluid-Flowing.patch diff --git a/patches/server-unmapped/0284-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch b/patches/server-unmapped-old/0001/0284-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch similarity index 100% rename from patches/server-unmapped/0284-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch rename to patches/server-unmapped-old/0001/0284-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch diff --git a/patches/server-unmapped/0285-Prevent-Mob-AI-Rules-from-Loading-Chunks.patch b/patches/server-unmapped-old/0001/0285-Prevent-Mob-AI-Rules-from-Loading-Chunks.patch similarity index 100% rename from patches/server-unmapped/0285-Prevent-Mob-AI-Rules-from-Loading-Chunks.patch rename to patches/server-unmapped-old/0001/0285-Prevent-Mob-AI-Rules-from-Loading-Chunks.patch diff --git a/patches/server-unmapped/0286-Prevent-mob-spawning-from-loading-generating-chunks.patch b/patches/server-unmapped-old/0001/0286-Prevent-mob-spawning-from-loading-generating-chunks.patch similarity index 100% rename from patches/server-unmapped/0286-Prevent-mob-spawning-from-loading-generating-chunks.patch rename to patches/server-unmapped-old/0001/0286-Prevent-mob-spawning-from-loading-generating-chunks.patch diff --git a/patches/server-unmapped/0287-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch b/patches/server-unmapped-old/0001/0287-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch similarity index 100% rename from patches/server-unmapped/0287-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch rename to patches/server-unmapped-old/0001/0287-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch diff --git a/patches/server-unmapped/0288-Implement-furnace-cook-speed-multiplier-API.patch b/patches/server-unmapped-old/0001/0288-Implement-furnace-cook-speed-multiplier-API.patch similarity index 100% rename from patches/server-unmapped/0288-Implement-furnace-cook-speed-multiplier-API.patch rename to patches/server-unmapped-old/0001/0288-Implement-furnace-cook-speed-multiplier-API.patch diff --git a/patches/server-unmapped/0289-PreSpawnerSpawnEvent.patch b/patches/server-unmapped-old/0001/0289-PreSpawnerSpawnEvent.patch similarity index 100% rename from patches/server-unmapped/0289-PreSpawnerSpawnEvent.patch rename to patches/server-unmapped-old/0001/0289-PreSpawnerSpawnEvent.patch diff --git a/patches/server-unmapped/0290-Catch-JsonParseException-in-Entity-and-TE-names.patch b/patches/server-unmapped-old/0001/0290-Catch-JsonParseException-in-Entity-and-TE-names.patch similarity index 100% rename from patches/server-unmapped/0290-Catch-JsonParseException-in-Entity-and-TE-names.patch rename to patches/server-unmapped-old/0001/0290-Catch-JsonParseException-in-Entity-and-TE-names.patch diff --git a/patches/server-unmapped/0291-Honor-EntityAgeable.ageLock.patch b/patches/server-unmapped-old/0001/0291-Honor-EntityAgeable.ageLock.patch similarity index 100% rename from patches/server-unmapped/0291-Honor-EntityAgeable.ageLock.patch rename to patches/server-unmapped-old/0001/0291-Honor-EntityAgeable.ageLock.patch diff --git a/patches/server-unmapped/0292-Configurable-connection-throttle-kick-message.patch b/patches/server-unmapped-old/0001/0292-Configurable-connection-throttle-kick-message.patch similarity index 100% rename from patches/server-unmapped/0292-Configurable-connection-throttle-kick-message.patch rename to patches/server-unmapped-old/0001/0292-Configurable-connection-throttle-kick-message.patch diff --git a/patches/server-unmapped/0293-Hook-into-CB-plugin-rewrites.patch b/patches/server-unmapped-old/0001/0293-Hook-into-CB-plugin-rewrites.patch similarity index 100% rename from patches/server-unmapped/0293-Hook-into-CB-plugin-rewrites.patch rename to patches/server-unmapped-old/0001/0293-Hook-into-CB-plugin-rewrites.patch diff --git a/patches/server-unmapped/0294-Allow-setting-the-vex-s-summoner.patch b/patches/server-unmapped-old/0001/0294-Allow-setting-the-vex-s-summoner.patch similarity index 100% rename from patches/server-unmapped/0294-Allow-setting-the-vex-s-summoner.patch rename to patches/server-unmapped-old/0001/0294-Allow-setting-the-vex-s-summoner.patch diff --git a/patches/server-unmapped/0295-Add-sun-related-API.patch b/patches/server-unmapped-old/0001/0295-Add-sun-related-API.patch similarity index 100% rename from patches/server-unmapped/0295-Add-sun-related-API.patch rename to patches/server-unmapped-old/0001/0295-Add-sun-related-API.patch diff --git a/patches/server-unmapped/0296-Turtle-API.patch b/patches/server-unmapped-old/0001/0296-Turtle-API.patch similarity index 100% rename from patches/server-unmapped/0296-Turtle-API.patch rename to patches/server-unmapped-old/0001/0296-Turtle-API.patch diff --git a/patches/server-unmapped/0297-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch b/patches/server-unmapped-old/0001/0297-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch similarity index 100% rename from patches/server-unmapped/0297-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch rename to patches/server-unmapped-old/0001/0297-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch diff --git a/patches/server-unmapped/0298-Call-player-spectator-target-events-and-improve-impl.patch b/patches/server-unmapped-old/0001/0298-Call-player-spectator-target-events-and-improve-impl.patch similarity index 100% rename from patches/server-unmapped/0298-Call-player-spectator-target-events-and-improve-impl.patch rename to patches/server-unmapped-old/0001/0298-Call-player-spectator-target-events-and-improve-impl.patch diff --git a/patches/server-unmapped/0299-Add-Velocity-IP-Forwarding-Support.patch b/patches/server-unmapped-old/0001/0299-Add-Velocity-IP-Forwarding-Support.patch similarity index 100% rename from patches/server-unmapped/0299-Add-Velocity-IP-Forwarding-Support.patch rename to patches/server-unmapped-old/0001/0299-Add-Velocity-IP-Forwarding-Support.patch diff --git a/patches/server-unmapped/0300-Add-more-Witch-API.patch b/patches/server-unmapped-old/0001/0300-Add-more-Witch-API.patch similarity index 100% rename from patches/server-unmapped/0300-Add-more-Witch-API.patch rename to patches/server-unmapped-old/0001/0300-Add-more-Witch-API.patch diff --git a/patches/server-unmapped/0301-Check-Drowned-for-Villager-Aggression-Config.patch b/patches/server-unmapped-old/0001/0301-Check-Drowned-for-Villager-Aggression-Config.patch similarity index 100% rename from patches/server-unmapped/0301-Check-Drowned-for-Villager-Aggression-Config.patch rename to patches/server-unmapped-old/0001/0301-Check-Drowned-for-Villager-Aggression-Config.patch diff --git a/patches/server-unmapped/0302-Here-s-Johnny.patch b/patches/server-unmapped-old/0001/0302-Here-s-Johnny.patch similarity index 100% rename from patches/server-unmapped/0302-Here-s-Johnny.patch rename to patches/server-unmapped-old/0001/0302-Here-s-Johnny.patch diff --git a/patches/server-unmapped/0303-Add-option-to-prevent-players-from-moving-into-unloa.patch b/patches/server-unmapped-old/0001/0303-Add-option-to-prevent-players-from-moving-into-unloa.patch similarity index 100% rename from patches/server-unmapped/0303-Add-option-to-prevent-players-from-moving-into-unloa.patch rename to patches/server-unmapped-old/0001/0303-Add-option-to-prevent-players-from-moving-into-unloa.patch diff --git a/patches/server-unmapped/0304-Reset-players-airTicks-on-respawn.patch b/patches/server-unmapped-old/0001/0304-Reset-players-airTicks-on-respawn.patch similarity index 100% rename from patches/server-unmapped/0304-Reset-players-airTicks-on-respawn.patch rename to patches/server-unmapped-old/0001/0304-Reset-players-airTicks-on-respawn.patch diff --git a/patches/server-unmapped/0305-Don-t-sleep-after-profile-lookups-if-not-needed.patch b/patches/server-unmapped-old/0001/0305-Don-t-sleep-after-profile-lookups-if-not-needed.patch similarity index 100% rename from patches/server-unmapped/0305-Don-t-sleep-after-profile-lookups-if-not-needed.patch rename to patches/server-unmapped-old/0001/0305-Don-t-sleep-after-profile-lookups-if-not-needed.patch diff --git a/patches/server-unmapped/0306-Improve-Server-Thread-Pool-and-Thread-Priorities.patch b/patches/server-unmapped-old/0001/0306-Improve-Server-Thread-Pool-and-Thread-Priorities.patch similarity index 100% rename from patches/server-unmapped/0306-Improve-Server-Thread-Pool-and-Thread-Priorities.patch rename to patches/server-unmapped-old/0001/0306-Improve-Server-Thread-Pool-and-Thread-Priorities.patch diff --git a/patches/server-unmapped/0307-Optimize-World-Time-Updates.patch b/patches/server-unmapped-old/0001/0307-Optimize-World-Time-Updates.patch similarity index 100% rename from patches/server-unmapped/0307-Optimize-World-Time-Updates.patch rename to patches/server-unmapped-old/0001/0307-Optimize-World-Time-Updates.patch diff --git a/patches/server-unmapped/0308-Restore-custom-InventoryHolder-support.patch b/patches/server-unmapped-old/0001/0308-Restore-custom-InventoryHolder-support.patch similarity index 100% rename from patches/server-unmapped/0308-Restore-custom-InventoryHolder-support.patch rename to patches/server-unmapped-old/0001/0308-Restore-custom-InventoryHolder-support.patch diff --git a/patches/server-unmapped/0309-Use-Vanilla-Minecart-Speeds.patch b/patches/server-unmapped-old/0001/0309-Use-Vanilla-Minecart-Speeds.patch similarity index 100% rename from patches/server-unmapped/0309-Use-Vanilla-Minecart-Speeds.patch rename to patches/server-unmapped-old/0001/0309-Use-Vanilla-Minecart-Speeds.patch diff --git a/patches/server-unmapped/0310-Fix-SpongeAbsortEvent-handling.patch b/patches/server-unmapped-old/0001/0310-Fix-SpongeAbsortEvent-handling.patch similarity index 100% rename from patches/server-unmapped/0310-Fix-SpongeAbsortEvent-handling.patch rename to patches/server-unmapped-old/0001/0310-Fix-SpongeAbsortEvent-handling.patch diff --git a/patches/server-unmapped/0311-Don-t-allow-digging-into-unloaded-chunks.patch b/patches/server-unmapped-old/0001/0311-Don-t-allow-digging-into-unloaded-chunks.patch similarity index 100% rename from patches/server-unmapped/0311-Don-t-allow-digging-into-unloaded-chunks.patch rename to patches/server-unmapped-old/0001/0311-Don-t-allow-digging-into-unloaded-chunks.patch diff --git a/patches/server-unmapped/0312-Book-Size-Limits.patch b/patches/server-unmapped-old/0001/0312-Book-Size-Limits.patch similarity index 100% rename from patches/server-unmapped/0312-Book-Size-Limits.patch rename to patches/server-unmapped-old/0001/0312-Book-Size-Limits.patch diff --git a/patches/server-unmapped/0313-Make-the-default-permission-message-configurable.patch b/patches/server-unmapped-old/0001/0313-Make-the-default-permission-message-configurable.patch similarity index 100% rename from patches/server-unmapped/0313-Make-the-default-permission-message-configurable.patch rename to patches/server-unmapped-old/0001/0313-Make-the-default-permission-message-configurable.patch diff --git a/patches/server-unmapped/0314-Prevent-rayTrace-from-loading-chunks.patch b/patches/server-unmapped-old/0001/0314-Prevent-rayTrace-from-loading-chunks.patch similarity index 100% rename from patches/server-unmapped/0314-Prevent-rayTrace-from-loading-chunks.patch rename to patches/server-unmapped-old/0001/0314-Prevent-rayTrace-from-loading-chunks.patch diff --git a/patches/server-unmapped/0315-Handle-Large-Packets-disconnecting-client.patch b/patches/server-unmapped-old/0001/0315-Handle-Large-Packets-disconnecting-client.patch similarity index 100% rename from patches/server-unmapped/0315-Handle-Large-Packets-disconnecting-client.patch rename to patches/server-unmapped-old/0001/0315-Handle-Large-Packets-disconnecting-client.patch diff --git a/patches/server-unmapped/0316-force-entity-dismount-during-teleportation.patch b/patches/server-unmapped-old/0001/0316-force-entity-dismount-during-teleportation.patch similarity index 100% rename from patches/server-unmapped/0316-force-entity-dismount-during-teleportation.patch rename to patches/server-unmapped-old/0001/0316-force-entity-dismount-during-teleportation.patch diff --git a/patches/server-unmapped/0317-Add-more-Zombie-API.patch b/patches/server-unmapped-old/0001/0317-Add-more-Zombie-API.patch similarity index 100% rename from patches/server-unmapped/0317-Add-more-Zombie-API.patch rename to patches/server-unmapped-old/0001/0317-Add-more-Zombie-API.patch diff --git a/patches/server-unmapped/0318-Add-PlayerConnectionCloseEvent.patch b/patches/server-unmapped-old/0001/0318-Add-PlayerConnectionCloseEvent.patch similarity index 100% rename from patches/server-unmapped/0318-Add-PlayerConnectionCloseEvent.patch rename to patches/server-unmapped-old/0001/0318-Add-PlayerConnectionCloseEvent.patch diff --git a/patches/server-unmapped/0319-Prevent-Enderman-from-loading-chunks.patch b/patches/server-unmapped-old/0001/0319-Prevent-Enderman-from-loading-chunks.patch similarity index 100% rename from patches/server-unmapped/0319-Prevent-Enderman-from-loading-chunks.patch rename to patches/server-unmapped-old/0001/0319-Prevent-Enderman-from-loading-chunks.patch diff --git a/patches/server-unmapped/0320-Add-APIs-to-replace-OfflinePlayer-getLastPlayed.patch b/patches/server-unmapped-old/0001/0320-Add-APIs-to-replace-OfflinePlayer-getLastPlayed.patch similarity index 100% rename from patches/server-unmapped/0320-Add-APIs-to-replace-OfflinePlayer-getLastPlayed.patch rename to patches/server-unmapped-old/0001/0320-Add-APIs-to-replace-OfflinePlayer-getLastPlayed.patch diff --git a/patches/server-unmapped/0321-Workaround-for-vehicle-tracking-issue-on-disconnect.patch b/patches/server-unmapped-old/0001/0321-Workaround-for-vehicle-tracking-issue-on-disconnect.patch similarity index 100% rename from patches/server-unmapped/0321-Workaround-for-vehicle-tracking-issue-on-disconnect.patch rename to patches/server-unmapped-old/0001/0321-Workaround-for-vehicle-tracking-issue-on-disconnect.patch diff --git a/patches/server-unmapped/0322-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch b/patches/server-unmapped-old/0001/0322-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch similarity index 100% rename from patches/server-unmapped/0322-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch rename to patches/server-unmapped-old/0001/0322-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch diff --git a/patches/server-unmapped/0323-Block-Entity-remove-from-being-called-on-Players.patch b/patches/server-unmapped-old/0001/0323-Block-Entity-remove-from-being-called-on-Players.patch similarity index 100% rename from patches/server-unmapped/0323-Block-Entity-remove-from-being-called-on-Players.patch rename to patches/server-unmapped-old/0001/0323-Block-Entity-remove-from-being-called-on-Players.patch diff --git a/patches/server-unmapped/0324-BlockDestroyEvent.patch b/patches/server-unmapped-old/0001/0324-BlockDestroyEvent.patch similarity index 100% rename from patches/server-unmapped/0324-BlockDestroyEvent.patch rename to patches/server-unmapped-old/0001/0324-BlockDestroyEvent.patch diff --git a/patches/server-unmapped/0325-Fix-Custom-Shapeless-Custom-Crafting-Recipes.patch b/patches/server-unmapped-old/0001/0325-Fix-Custom-Shapeless-Custom-Crafting-Recipes.patch similarity index 100% rename from patches/server-unmapped/0325-Fix-Custom-Shapeless-Custom-Crafting-Recipes.patch rename to patches/server-unmapped-old/0001/0325-Fix-Custom-Shapeless-Custom-Crafting-Recipes.patch diff --git a/patches/server-unmapped/0326-Fix-sign-edit-memory-leak.patch b/patches/server-unmapped-old/0001/0326-Fix-sign-edit-memory-leak.patch similarity index 100% rename from patches/server-unmapped/0326-Fix-sign-edit-memory-leak.patch rename to patches/server-unmapped-old/0001/0326-Fix-sign-edit-memory-leak.patch diff --git a/patches/server-unmapped/0327-Limit-Client-Sign-length-more.patch b/patches/server-unmapped-old/0001/0327-Limit-Client-Sign-length-more.patch similarity index 100% rename from patches/server-unmapped/0327-Limit-Client-Sign-length-more.patch rename to patches/server-unmapped-old/0001/0327-Limit-Client-Sign-length-more.patch diff --git a/patches/server-unmapped/0328-Don-t-check-ConvertSigns-boolean-every-sign-save.patch b/patches/server-unmapped-old/0001/0328-Don-t-check-ConvertSigns-boolean-every-sign-save.patch similarity index 100% rename from patches/server-unmapped/0328-Don-t-check-ConvertSigns-boolean-every-sign-save.patch rename to patches/server-unmapped-old/0001/0328-Don-t-check-ConvertSigns-boolean-every-sign-save.patch diff --git a/patches/server-unmapped/0329-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/patches/server-unmapped-old/0001/0329-Optimize-Network-Manager-and-add-advanced-packet-sup.patch similarity index 100% rename from patches/server-unmapped/0329-Optimize-Network-Manager-and-add-advanced-packet-sup.patch rename to patches/server-unmapped-old/0001/0329-Optimize-Network-Manager-and-add-advanced-packet-sup.patch diff --git a/patches/server-unmapped/0330-Handle-Oversized-Tile-Entities-in-chunks.patch b/patches/server-unmapped-old/0001/0330-Handle-Oversized-Tile-Entities-in-chunks.patch similarity index 100% rename from patches/server-unmapped/0330-Handle-Oversized-Tile-Entities-in-chunks.patch rename to patches/server-unmapped-old/0001/0330-Handle-Oversized-Tile-Entities-in-chunks.patch diff --git a/patches/server-unmapped/0331-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch b/patches/server-unmapped-old/0001/0331-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch similarity index 100% rename from patches/server-unmapped/0331-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch rename to patches/server-unmapped-old/0001/0331-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch diff --git a/patches/server-unmapped/0332-Set-Zombie-last-tick-at-start-of-drowning-process.patch b/patches/server-unmapped-old/0001/0332-Set-Zombie-last-tick-at-start-of-drowning-process.patch similarity index 100% rename from patches/server-unmapped/0332-Set-Zombie-last-tick-at-start-of-drowning-process.patch rename to patches/server-unmapped-old/0001/0332-Set-Zombie-last-tick-at-start-of-drowning-process.patch diff --git a/patches/server-unmapped/0333-Allow-Saving-of-Oversized-Chunks.patch b/patches/server-unmapped-old/0001/0333-Allow-Saving-of-Oversized-Chunks.patch similarity index 100% rename from patches/server-unmapped/0333-Allow-Saving-of-Oversized-Chunks.patch rename to patches/server-unmapped-old/0001/0333-Allow-Saving-of-Oversized-Chunks.patch diff --git a/patches/server-unmapped/0334-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch b/patches/server-unmapped-old/0001/0334-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch similarity index 100% rename from patches/server-unmapped/0334-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch rename to patches/server-unmapped-old/0001/0334-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch diff --git a/patches/server-unmapped/0335-Add-LivingEntity-getTargetEntity.patch b/patches/server-unmapped-old/0001/0335-Add-LivingEntity-getTargetEntity.patch similarity index 100% rename from patches/server-unmapped/0335-Add-LivingEntity-getTargetEntity.patch rename to patches/server-unmapped-old/0001/0335-Add-LivingEntity-getTargetEntity.patch diff --git a/patches/server-unmapped/0336-Use-proper-max-length-when-serialising-BungeeCord-te.patch b/patches/server-unmapped-old/0001/0336-Use-proper-max-length-when-serialising-BungeeCord-te.patch similarity index 100% rename from patches/server-unmapped/0336-Use-proper-max-length-when-serialising-BungeeCord-te.patch rename to patches/server-unmapped-old/0001/0336-Use-proper-max-length-when-serialising-BungeeCord-te.patch diff --git a/patches/server-unmapped/0337-Entity-getEntitySpawnReason.patch b/patches/server-unmapped-old/0001/0337-Entity-getEntitySpawnReason.patch similarity index 100% rename from patches/server-unmapped/0337-Entity-getEntitySpawnReason.patch rename to patches/server-unmapped-old/0001/0337-Entity-getEntitySpawnReason.patch diff --git a/patches/server-unmapped/0338-Update-entity-Metadata-for-all-tracked-players.patch b/patches/server-unmapped-old/0001/0338-Update-entity-Metadata-for-all-tracked-players.patch similarity index 100% rename from patches/server-unmapped/0338-Update-entity-Metadata-for-all-tracked-players.patch rename to patches/server-unmapped-old/0001/0338-Update-entity-Metadata-for-all-tracked-players.patch diff --git a/patches/server-unmapped/0339-Implement-PlayerPostRespawnEvent.patch b/patches/server-unmapped-old/0001/0339-Implement-PlayerPostRespawnEvent.patch similarity index 100% rename from patches/server-unmapped/0339-Implement-PlayerPostRespawnEvent.patch rename to patches/server-unmapped-old/0001/0339-Implement-PlayerPostRespawnEvent.patch diff --git a/patches/server-unmapped/0340-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch b/patches/server-unmapped-old/0001/0340-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch similarity index 100% rename from patches/server-unmapped/0340-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch rename to patches/server-unmapped-old/0001/0340-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch diff --git a/patches/server-unmapped/0341-Server-Tick-Events.patch b/patches/server-unmapped-old/0001/0341-Server-Tick-Events.patch similarity index 100% rename from patches/server-unmapped/0341-Server-Tick-Events.patch rename to patches/server-unmapped-old/0001/0341-Server-Tick-Events.patch diff --git a/patches/server-unmapped/0342-PlayerDeathEvent-getItemsToKeep.patch b/patches/server-unmapped-old/0001/0342-PlayerDeathEvent-getItemsToKeep.patch similarity index 100% rename from patches/server-unmapped/0342-PlayerDeathEvent-getItemsToKeep.patch rename to patches/server-unmapped-old/0001/0342-PlayerDeathEvent-getItemsToKeep.patch diff --git a/patches/server-unmapped/0343-Optimize-Captured-TileEntity-Lookup.patch b/patches/server-unmapped-old/0001/0343-Optimize-Captured-TileEntity-Lookup.patch similarity index 100% rename from patches/server-unmapped/0343-Optimize-Captured-TileEntity-Lookup.patch rename to patches/server-unmapped-old/0001/0343-Optimize-Captured-TileEntity-Lookup.patch diff --git a/patches/server-unmapped/0344-Add-Heightmap-API.patch b/patches/server-unmapped-old/0001/0344-Add-Heightmap-API.patch similarity index 100% rename from patches/server-unmapped/0344-Add-Heightmap-API.patch rename to patches/server-unmapped-old/0001/0344-Add-Heightmap-API.patch diff --git a/patches/server-unmapped/0345-Mob-Spawner-API-Enhancements.patch b/patches/server-unmapped-old/0001/0345-Mob-Spawner-API-Enhancements.patch similarity index 100% rename from patches/server-unmapped/0345-Mob-Spawner-API-Enhancements.patch rename to patches/server-unmapped-old/0001/0345-Mob-Spawner-API-Enhancements.patch diff --git a/patches/server-unmapped/0346-Per-Player-View-Distance-API-placeholders.patch b/patches/server-unmapped-old/0001/0346-Per-Player-View-Distance-API-placeholders.patch similarity index 100% rename from patches/server-unmapped/0346-Per-Player-View-Distance-API-placeholders.patch rename to patches/server-unmapped-old/0001/0346-Per-Player-View-Distance-API-placeholders.patch diff --git a/patches/server-unmapped/0347-Fix-CB-call-to-changed-postToMainThread-method.patch b/patches/server-unmapped-old/0001/0347-Fix-CB-call-to-changed-postToMainThread-method.patch similarity index 100% rename from patches/server-unmapped/0347-Fix-CB-call-to-changed-postToMainThread-method.patch rename to patches/server-unmapped-old/0001/0347-Fix-CB-call-to-changed-postToMainThread-method.patch diff --git a/patches/server-unmapped/0348-Fix-sounds-when-item-frames-are-modified-MC-123450.patch b/patches/server-unmapped-old/0001/0348-Fix-sounds-when-item-frames-are-modified-MC-123450.patch similarity index 100% rename from patches/server-unmapped/0348-Fix-sounds-when-item-frames-are-modified-MC-123450.patch rename to patches/server-unmapped-old/0001/0348-Fix-sounds-when-item-frames-are-modified-MC-123450.patch diff --git a/patches/server-unmapped/0349-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch b/patches/server-unmapped-old/0001/0349-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch similarity index 100% rename from patches/server-unmapped/0349-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch rename to patches/server-unmapped-old/0001/0349-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch diff --git a/patches/server-unmapped/0350-Fix-issues-with-entity-loss-due-to-unloaded-chunks.patch b/patches/server-unmapped-old/0001/0350-Fix-issues-with-entity-loss-due-to-unloaded-chunks.patch similarity index 100% rename from patches/server-unmapped/0350-Fix-issues-with-entity-loss-due-to-unloaded-chunks.patch rename to patches/server-unmapped-old/0001/0350-Fix-issues-with-entity-loss-due-to-unloaded-chunks.patch diff --git a/patches/server-unmapped/0351-Duplicate-UUID-Resolve-Option.patch b/patches/server-unmapped-old/0001/0351-Duplicate-UUID-Resolve-Option.patch similarity index 100% rename from patches/server-unmapped/0351-Duplicate-UUID-Resolve-Option.patch rename to patches/server-unmapped-old/0001/0351-Duplicate-UUID-Resolve-Option.patch diff --git a/patches/server-unmapped/0352-improve-CraftWorld-isChunkLoaded.patch b/patches/server-unmapped-old/0001/0352-improve-CraftWorld-isChunkLoaded.patch similarity index 100% rename from patches/server-unmapped/0352-improve-CraftWorld-isChunkLoaded.patch rename to patches/server-unmapped-old/0001/0352-improve-CraftWorld-isChunkLoaded.patch diff --git a/patches/server-unmapped/0353-Configurable-Keep-Spawn-Loaded-range-per-world.patch b/patches/server-unmapped-old/0001/0353-Configurable-Keep-Spawn-Loaded-range-per-world.patch similarity index 100% rename from patches/server-unmapped/0353-Configurable-Keep-Spawn-Loaded-range-per-world.patch rename to patches/server-unmapped-old/0001/0353-Configurable-Keep-Spawn-Loaded-range-per-world.patch diff --git a/patches/server-unmapped/0354-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch b/patches/server-unmapped-old/0001/0354-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch similarity index 100% rename from patches/server-unmapped/0354-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch rename to patches/server-unmapped-old/0001/0354-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch diff --git a/patches/server-unmapped/0355-ChunkMapDistance-CME.patch b/patches/server-unmapped-old/0001/0355-ChunkMapDistance-CME.patch similarity index 100% rename from patches/server-unmapped/0355-ChunkMapDistance-CME.patch rename to patches/server-unmapped-old/0001/0355-ChunkMapDistance-CME.patch diff --git a/patches/server-unmapped/0356-Implement-CraftBlockSoundGroup.patch b/patches/server-unmapped-old/0001/0356-Implement-CraftBlockSoundGroup.patch similarity index 100% rename from patches/server-unmapped/0356-Implement-CraftBlockSoundGroup.patch rename to patches/server-unmapped-old/0001/0356-Implement-CraftBlockSoundGroup.patch diff --git a/patches/server-unmapped/0357-Chunk-debug-command.patch b/patches/server-unmapped-old/0001/0357-Chunk-debug-command.patch similarity index 100% rename from patches/server-unmapped/0357-Chunk-debug-command.patch rename to patches/server-unmapped-old/0001/0357-Chunk-debug-command.patch diff --git a/patches/server-unmapped/0358-Catch-exceptions-from-dispenser-entity-spawns.patch b/patches/server-unmapped-old/0001/0358-Catch-exceptions-from-dispenser-entity-spawns.patch similarity index 100% rename from patches/server-unmapped/0358-Catch-exceptions-from-dispenser-entity-spawns.patch rename to patches/server-unmapped-old/0001/0358-Catch-exceptions-from-dispenser-entity-spawns.patch diff --git a/patches/server-unmapped/0359-Fix-World-isChunkGenerated-calls.patch b/patches/server-unmapped-old/0001/0359-Fix-World-isChunkGenerated-calls.patch similarity index 100% rename from patches/server-unmapped/0359-Fix-World-isChunkGenerated-calls.patch rename to patches/server-unmapped-old/0001/0359-Fix-World-isChunkGenerated-calls.patch diff --git a/patches/server-unmapped/0360-Show-blockstate-location-if-we-failed-to-read-it.patch b/patches/server-unmapped-old/0001/0360-Show-blockstate-location-if-we-failed-to-read-it.patch similarity index 100% rename from patches/server-unmapped/0360-Show-blockstate-location-if-we-failed-to-read-it.patch rename to patches/server-unmapped-old/0001/0360-Show-blockstate-location-if-we-failed-to-read-it.patch diff --git a/patches/server-unmapped/0361-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch b/patches/server-unmapped-old/0001/0361-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch similarity index 100% rename from patches/server-unmapped/0361-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch rename to patches/server-unmapped-old/0001/0361-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch diff --git a/patches/server-unmapped/0362-incremental-chunk-saving.patch b/patches/server-unmapped-old/0001/0362-incremental-chunk-saving.patch similarity index 100% rename from patches/server-unmapped/0362-incremental-chunk-saving.patch rename to patches/server-unmapped-old/0001/0362-incremental-chunk-saving.patch diff --git a/patches/server-unmapped/0363-Anti-Xray.patch b/patches/server-unmapped-old/0001/0363-Anti-Xray.patch similarity index 100% rename from patches/server-unmapped/0363-Anti-Xray.patch rename to patches/server-unmapped-old/0001/0363-Anti-Xray.patch diff --git a/patches/server-unmapped/0364-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch b/patches/server-unmapped-old/0001/0364-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch similarity index 100% rename from patches/server-unmapped/0364-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch rename to patches/server-unmapped-old/0001/0364-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch diff --git a/patches/server-unmapped/0365-Configurable-projectile-relative-velocity.patch b/patches/server-unmapped-old/0001/0365-Configurable-projectile-relative-velocity.patch similarity index 100% rename from patches/server-unmapped/0365-Configurable-projectile-relative-velocity.patch rename to patches/server-unmapped-old/0001/0365-Configurable-projectile-relative-velocity.patch diff --git a/patches/server-unmapped/0366-Mark-entities-as-being-ticked-when-notifying-navigat.patch b/patches/server-unmapped-old/0001/0366-Mark-entities-as-being-ticked-when-notifying-navigat.patch similarity index 100% rename from patches/server-unmapped/0366-Mark-entities-as-being-ticked-when-notifying-navigat.patch rename to patches/server-unmapped-old/0001/0366-Mark-entities-as-being-ticked-when-notifying-navigat.patch diff --git a/patches/server-unmapped/0367-offset-item-frame-ticking.patch b/patches/server-unmapped-old/0001/0367-offset-item-frame-ticking.patch similarity index 100% rename from patches/server-unmapped/0367-offset-item-frame-ticking.patch rename to patches/server-unmapped-old/0001/0367-offset-item-frame-ticking.patch diff --git a/patches/server-unmapped/0368-Avoid-hopper-searches-if-there-are-no-items.patch b/patches/server-unmapped-old/0001/0368-Avoid-hopper-searches-if-there-are-no-items.patch similarity index 100% rename from patches/server-unmapped/0368-Avoid-hopper-searches-if-there-are-no-items.patch rename to patches/server-unmapped-old/0001/0368-Avoid-hopper-searches-if-there-are-no-items.patch diff --git a/patches/server-unmapped/0369-Asynchronous-chunk-IO-and-loading.patch b/patches/server-unmapped-old/0001/0369-Asynchronous-chunk-IO-and-loading.patch similarity index 100% rename from patches/server-unmapped/0369-Asynchronous-chunk-IO-and-loading.patch rename to patches/server-unmapped-old/0001/0369-Asynchronous-chunk-IO-and-loading.patch diff --git a/patches/server-unmapped/0370-Use-getChunkIfLoadedImmediately-in-places.patch b/patches/server-unmapped-old/0001/0370-Use-getChunkIfLoadedImmediately-in-places.patch similarity index 100% rename from patches/server-unmapped/0370-Use-getChunkIfLoadedImmediately-in-places.patch rename to patches/server-unmapped-old/0001/0370-Use-getChunkIfLoadedImmediately-in-places.patch diff --git a/patches/server-unmapped/0371-Reduce-sync-loads.patch b/patches/server-unmapped-old/0001/0371-Reduce-sync-loads.patch similarity index 100% rename from patches/server-unmapped/0371-Reduce-sync-loads.patch rename to patches/server-unmapped-old/0001/0371-Reduce-sync-loads.patch diff --git a/patches/server-unmapped/0372-Implement-alternative-item-despawn-rate.patch b/patches/server-unmapped-old/0001/0372-Implement-alternative-item-despawn-rate.patch similarity index 100% rename from patches/server-unmapped/0372-Implement-alternative-item-despawn-rate.patch rename to patches/server-unmapped-old/0001/0372-Implement-alternative-item-despawn-rate.patch diff --git a/patches/server-unmapped/0373-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch b/patches/server-unmapped-old/0001/0373-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch similarity index 100% rename from patches/server-unmapped/0373-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch rename to patches/server-unmapped-old/0001/0373-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch diff --git a/patches/server-unmapped/0374-Fix-MC-158900.patch b/patches/server-unmapped-old/0001/0374-Fix-MC-158900.patch similarity index 100% rename from patches/server-unmapped/0374-Fix-MC-158900.patch rename to patches/server-unmapped-old/0001/0374-Fix-MC-158900.patch diff --git a/patches/server-unmapped/0375-implement-optional-per-player-mob-spawns.patch b/patches/server-unmapped-old/0001/0375-implement-optional-per-player-mob-spawns.patch similarity index 100% rename from patches/server-unmapped/0375-implement-optional-per-player-mob-spawns.patch rename to patches/server-unmapped-old/0001/0375-implement-optional-per-player-mob-spawns.patch diff --git a/patches/server-unmapped/0376-Prevent-consuming-the-wrong-itemstack.patch b/patches/server-unmapped-old/0001/0376-Prevent-consuming-the-wrong-itemstack.patch similarity index 100% rename from patches/server-unmapped/0376-Prevent-consuming-the-wrong-itemstack.patch rename to patches/server-unmapped-old/0001/0376-Prevent-consuming-the-wrong-itemstack.patch diff --git a/patches/server-unmapped/0377-Fix-nether-portal-creation.patch b/patches/server-unmapped-old/0001/0377-Fix-nether-portal-creation.patch similarity index 100% rename from patches/server-unmapped/0377-Fix-nether-portal-creation.patch rename to patches/server-unmapped-old/0001/0377-Fix-nether-portal-creation.patch diff --git a/patches/server-unmapped/0378-Generator-Settings.patch b/patches/server-unmapped-old/0001/0378-Generator-Settings.patch similarity index 100% rename from patches/server-unmapped/0378-Generator-Settings.patch rename to patches/server-unmapped-old/0001/0378-Generator-Settings.patch diff --git a/patches/server-unmapped/0379-Fix-MC-161754.patch b/patches/server-unmapped-old/0001/0379-Fix-MC-161754.patch similarity index 100% rename from patches/server-unmapped/0379-Fix-MC-161754.patch rename to patches/server-unmapped-old/0001/0379-Fix-MC-161754.patch diff --git a/patches/server-unmapped/0380-Performance-improvement-for-Chunk.getEntities.patch b/patches/server-unmapped-old/0001/0380-Performance-improvement-for-Chunk.getEntities.patch similarity index 100% rename from patches/server-unmapped/0380-Performance-improvement-for-Chunk.getEntities.patch rename to patches/server-unmapped-old/0001/0380-Performance-improvement-for-Chunk.getEntities.patch diff --git a/patches/server-unmapped/0381-Fix-spawning-of-hanging-entities-that-are-not-ItemFr.patch b/patches/server-unmapped-old/0001/0381-Fix-spawning-of-hanging-entities-that-are-not-ItemFr.patch similarity index 100% rename from patches/server-unmapped/0381-Fix-spawning-of-hanging-entities-that-are-not-ItemFr.patch rename to patches/server-unmapped-old/0001/0381-Fix-spawning-of-hanging-entities-that-are-not-ItemFr.patch diff --git a/patches/server-unmapped/0382-Expose-the-internal-current-tick.patch b/patches/server-unmapped-old/0001/0382-Expose-the-internal-current-tick.patch similarity index 100% rename from patches/server-unmapped/0382-Expose-the-internal-current-tick.patch rename to patches/server-unmapped-old/0001/0382-Expose-the-internal-current-tick.patch diff --git a/patches/server-unmapped/0383-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch b/patches/server-unmapped-old/0001/0383-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch similarity index 100% rename from patches/server-unmapped/0383-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch rename to patches/server-unmapped-old/0001/0383-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch diff --git a/patches/server-unmapped/0384-Add-option-to-disable-pillager-patrols.patch b/patches/server-unmapped-old/0001/0384-Add-option-to-disable-pillager-patrols.patch similarity index 100% rename from patches/server-unmapped/0384-Add-option-to-disable-pillager-patrols.patch rename to patches/server-unmapped-old/0001/0384-Add-option-to-disable-pillager-patrols.patch diff --git a/patches/server-unmapped/0385-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch b/patches/server-unmapped-old/0001/0385-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch similarity index 100% rename from patches/server-unmapped/0385-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch rename to patches/server-unmapped-old/0001/0385-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch diff --git a/patches/server-unmapped/0386-PlayerLaunchProjectileEvent.patch b/patches/server-unmapped-old/0001/0386-PlayerLaunchProjectileEvent.patch similarity index 100% rename from patches/server-unmapped/0386-PlayerLaunchProjectileEvent.patch rename to patches/server-unmapped-old/0001/0386-PlayerLaunchProjectileEvent.patch diff --git a/patches/server-unmapped/0387-Add-CraftMagicNumbers.isSupportedApiVersion.patch b/patches/server-unmapped-old/0001/0387-Add-CraftMagicNumbers.isSupportedApiVersion.patch similarity index 100% rename from patches/server-unmapped/0387-Add-CraftMagicNumbers.isSupportedApiVersion.patch rename to patches/server-unmapped-old/0001/0387-Add-CraftMagicNumbers.isSupportedApiVersion.patch diff --git a/patches/server-unmapped/0388-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch b/patches/server-unmapped-old/0001/0388-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch similarity index 100% rename from patches/server-unmapped/0388-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch rename to patches/server-unmapped-old/0001/0388-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch diff --git a/patches/server-unmapped/0389-MC-145656-Fix-Follow-Range-Initial-Target.patch b/patches/server-unmapped-old/0001/0389-MC-145656-Fix-Follow-Range-Initial-Target.patch similarity index 100% rename from patches/server-unmapped/0389-MC-145656-Fix-Follow-Range-Initial-Target.patch rename to patches/server-unmapped-old/0001/0389-MC-145656-Fix-Follow-Range-Initial-Target.patch diff --git a/patches/server-unmapped/0390-Optimize-Hoppers.patch b/patches/server-unmapped-old/0001/0390-Optimize-Hoppers.patch similarity index 100% rename from patches/server-unmapped/0390-Optimize-Hoppers.patch rename to patches/server-unmapped-old/0001/0390-Optimize-Hoppers.patch diff --git a/patches/server-unmapped/0391-PlayerDeathEvent-shouldDropExperience.patch b/patches/server-unmapped-old/0001/0391-PlayerDeathEvent-shouldDropExperience.patch similarity index 100% rename from patches/server-unmapped/0391-PlayerDeathEvent-shouldDropExperience.patch rename to patches/server-unmapped-old/0001/0391-PlayerDeathEvent-shouldDropExperience.patch diff --git a/patches/server-unmapped/0392-Prevent-bees-loading-chunks-checking-hive-position.patch b/patches/server-unmapped-old/0001/0392-Prevent-bees-loading-chunks-checking-hive-position.patch similarity index 100% rename from patches/server-unmapped/0392-Prevent-bees-loading-chunks-checking-hive-position.patch rename to patches/server-unmapped-old/0001/0392-Prevent-bees-loading-chunks-checking-hive-position.patch diff --git a/patches/server-unmapped/0393-Don-t-load-Chunks-from-Hoppers-and-other-things.patch b/patches/server-unmapped-old/0001/0393-Don-t-load-Chunks-from-Hoppers-and-other-things.patch similarity index 100% rename from patches/server-unmapped/0393-Don-t-load-Chunks-from-Hoppers-and-other-things.patch rename to patches/server-unmapped-old/0001/0393-Don-t-load-Chunks-from-Hoppers-and-other-things.patch diff --git a/patches/server-unmapped/0394-Guard-against-serializing-mismatching-chunk-coordina.patch b/patches/server-unmapped-old/0001/0394-Guard-against-serializing-mismatching-chunk-coordina.patch similarity index 100% rename from patches/server-unmapped/0394-Guard-against-serializing-mismatching-chunk-coordina.patch rename to patches/server-unmapped-old/0001/0394-Guard-against-serializing-mismatching-chunk-coordina.patch diff --git a/patches/server-unmapped/0395-Optimise-IEntityAccess-getPlayerByUUID.patch b/patches/server-unmapped-old/0001/0395-Optimise-IEntityAccess-getPlayerByUUID.patch similarity index 100% rename from patches/server-unmapped/0395-Optimise-IEntityAccess-getPlayerByUUID.patch rename to patches/server-unmapped-old/0001/0395-Optimise-IEntityAccess-getPlayerByUUID.patch diff --git a/patches/server-unmapped/0396-Fix-items-not-falling-correctly.patch b/patches/server-unmapped-old/0001/0396-Fix-items-not-falling-correctly.patch similarity index 100% rename from patches/server-unmapped/0396-Fix-items-not-falling-correctly.patch rename to patches/server-unmapped-old/0001/0396-Fix-items-not-falling-correctly.patch diff --git a/patches/server-unmapped/0397-Lag-compensate-eating.patch b/patches/server-unmapped-old/0001/0397-Lag-compensate-eating.patch similarity index 100% rename from patches/server-unmapped/0397-Lag-compensate-eating.patch rename to patches/server-unmapped-old/0001/0397-Lag-compensate-eating.patch diff --git a/patches/server-unmapped/0398-Optimize-call-to-getFluid-for-explosions.patch b/patches/server-unmapped-old/0001/0398-Optimize-call-to-getFluid-for-explosions.patch similarity index 100% rename from patches/server-unmapped/0398-Optimize-call-to-getFluid-for-explosions.patch rename to patches/server-unmapped-old/0001/0398-Optimize-call-to-getFluid-for-explosions.patch diff --git a/patches/server-unmapped/0399-Fix-last-firework-in-stack-not-having-effects-when-d.patch b/patches/server-unmapped-old/0001/0399-Fix-last-firework-in-stack-not-having-effects-when-d.patch similarity index 100% rename from patches/server-unmapped/0399-Fix-last-firework-in-stack-not-having-effects-when-d.patch rename to patches/server-unmapped-old/0001/0399-Fix-last-firework-in-stack-not-having-effects-when-d.patch diff --git a/patches/server-unmapped/0400-Add-effect-to-block-break-naturally.patch b/patches/server-unmapped-old/0001/0400-Add-effect-to-block-break-naturally.patch similarity index 100% rename from patches/server-unmapped/0400-Add-effect-to-block-break-naturally.patch rename to patches/server-unmapped-old/0001/0400-Add-effect-to-block-break-naturally.patch diff --git a/patches/server-unmapped/0401-Tracking-Range-Improvements.patch b/patches/server-unmapped-old/0001/0401-Tracking-Range-Improvements.patch similarity index 100% rename from patches/server-unmapped/0401-Tracking-Range-Improvements.patch rename to patches/server-unmapped-old/0001/0401-Tracking-Range-Improvements.patch diff --git a/patches/server-unmapped/0402-Entity-Activation-Range-2.0.patch b/patches/server-unmapped-old/0001/0402-Entity-Activation-Range-2.0.patch similarity index 100% rename from patches/server-unmapped/0402-Entity-Activation-Range-2.0.patch rename to patches/server-unmapped-old/0001/0402-Entity-Activation-Range-2.0.patch diff --git a/patches/server-unmapped/0403-Fix-items-vanishing-through-end-portal.patch b/patches/server-unmapped-old/0001/0403-Fix-items-vanishing-through-end-portal.patch similarity index 100% rename from patches/server-unmapped/0403-Fix-items-vanishing-through-end-portal.patch rename to patches/server-unmapped-old/0001/0403-Fix-items-vanishing-through-end-portal.patch diff --git a/patches/server-unmapped/0404-Bees-get-gravity-in-void.-Fixes-MC-167279.patch b/patches/server-unmapped-old/0001/0404-Bees-get-gravity-in-void.-Fixes-MC-167279.patch similarity index 100% rename from patches/server-unmapped/0404-Bees-get-gravity-in-void.-Fixes-MC-167279.patch rename to patches/server-unmapped-old/0001/0404-Bees-get-gravity-in-void.-Fixes-MC-167279.patch diff --git a/patches/server-unmapped/0405-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/patches/server-unmapped-old/0001/0405-Optimise-getChunkAt-calls-for-loaded-chunks.patch similarity index 100% rename from patches/server-unmapped/0405-Optimise-getChunkAt-calls-for-loaded-chunks.patch rename to patches/server-unmapped-old/0001/0405-Optimise-getChunkAt-calls-for-loaded-chunks.patch diff --git a/patches/server-unmapped/0406-Allow-overriding-the-java-version-check.patch b/patches/server-unmapped-old/0001/0406-Allow-overriding-the-java-version-check.patch similarity index 100% rename from patches/server-unmapped/0406-Allow-overriding-the-java-version-check.patch rename to patches/server-unmapped-old/0001/0406-Allow-overriding-the-java-version-check.patch diff --git a/patches/server-unmapped/0407-Add-ThrownEggHatchEvent.patch b/patches/server-unmapped-old/0001/0407-Add-ThrownEggHatchEvent.patch similarity index 100% rename from patches/server-unmapped/0407-Add-ThrownEggHatchEvent.patch rename to patches/server-unmapped-old/0001/0407-Add-ThrownEggHatchEvent.patch diff --git a/patches/server-unmapped/0408-Optimise-random-block-ticking.patch b/patches/server-unmapped-old/0001/0408-Optimise-random-block-ticking.patch similarity index 100% rename from patches/server-unmapped/0408-Optimise-random-block-ticking.patch rename to patches/server-unmapped-old/0001/0408-Optimise-random-block-ticking.patch diff --git a/patches/server-unmapped/0409-Entity-Jump-API.patch b/patches/server-unmapped-old/0001/0409-Entity-Jump-API.patch similarity index 100% rename from patches/server-unmapped/0409-Entity-Jump-API.patch rename to patches/server-unmapped-old/0001/0409-Entity-Jump-API.patch diff --git a/patches/server-unmapped/0410-Add-option-to-nerf-pigmen-from-nether-portals.patch b/patches/server-unmapped-old/0001/0410-Add-option-to-nerf-pigmen-from-nether-portals.patch similarity index 100% rename from patches/server-unmapped/0410-Add-option-to-nerf-pigmen-from-nether-portals.patch rename to patches/server-unmapped-old/0001/0410-Add-option-to-nerf-pigmen-from-nether-portals.patch diff --git a/patches/server-unmapped/0411-Make-the-GUI-graph-fancier.patch b/patches/server-unmapped-old/0001/0411-Make-the-GUI-graph-fancier.patch similarity index 100% rename from patches/server-unmapped/0411-Make-the-GUI-graph-fancier.patch rename to patches/server-unmapped-old/0001/0411-Make-the-GUI-graph-fancier.patch diff --git a/patches/server-unmapped/0412-add-hand-to-BlockMultiPlaceEvent.patch b/patches/server-unmapped-old/0001/0412-add-hand-to-BlockMultiPlaceEvent.patch similarity index 100% rename from patches/server-unmapped/0412-add-hand-to-BlockMultiPlaceEvent.patch rename to patches/server-unmapped-old/0001/0412-add-hand-to-BlockMultiPlaceEvent.patch diff --git a/patches/server-unmapped/0413-Prevent-teleporting-dead-entities.patch b/patches/server-unmapped-old/0001/0413-Prevent-teleporting-dead-entities.patch similarity index 100% rename from patches/server-unmapped/0413-Prevent-teleporting-dead-entities.patch rename to patches/server-unmapped-old/0001/0413-Prevent-teleporting-dead-entities.patch diff --git a/patches/server-unmapped/0414-Validate-tripwire-hook-placement-before-update.patch b/patches/server-unmapped-old/0001/0414-Validate-tripwire-hook-placement-before-update.patch similarity index 100% rename from patches/server-unmapped/0414-Validate-tripwire-hook-placement-before-update.patch rename to patches/server-unmapped-old/0001/0414-Validate-tripwire-hook-placement-before-update.patch diff --git a/patches/server-unmapped/0415-Add-option-to-allow-iron-golems-to-spawn-in-air.patch b/patches/server-unmapped-old/0001/0415-Add-option-to-allow-iron-golems-to-spawn-in-air.patch similarity index 100% rename from patches/server-unmapped/0415-Add-option-to-allow-iron-golems-to-spawn-in-air.patch rename to patches/server-unmapped-old/0001/0415-Add-option-to-allow-iron-golems-to-spawn-in-air.patch diff --git a/patches/server-unmapped/0416-Configurable-chance-of-villager-zombie-infection.patch b/patches/server-unmapped-old/0001/0416-Configurable-chance-of-villager-zombie-infection.patch similarity index 100% rename from patches/server-unmapped/0416-Configurable-chance-of-villager-zombie-infection.patch rename to patches/server-unmapped-old/0001/0416-Configurable-chance-of-villager-zombie-infection.patch diff --git a/patches/server-unmapped/0417-Optimise-Chunk-getFluid.patch b/patches/server-unmapped-old/0001/0417-Optimise-Chunk-getFluid.patch similarity index 100% rename from patches/server-unmapped/0417-Optimise-Chunk-getFluid.patch rename to patches/server-unmapped-old/0001/0417-Optimise-Chunk-getFluid.patch diff --git a/patches/server-unmapped/0418-Optimise-TickListServer-by-rewriting-it.patch b/patches/server-unmapped-old/0001/0418-Optimise-TickListServer-by-rewriting-it.patch similarity index 100% rename from patches/server-unmapped/0418-Optimise-TickListServer-by-rewriting-it.patch rename to patches/server-unmapped-old/0001/0418-Optimise-TickListServer-by-rewriting-it.patch diff --git a/patches/server-unmapped/0419-Pillager-patrol-spawn-settings-and-per-player-option.patch b/patches/server-unmapped-old/0001/0419-Pillager-patrol-spawn-settings-and-per-player-option.patch similarity index 100% rename from patches/server-unmapped/0419-Pillager-patrol-spawn-settings-and-per-player-option.patch rename to patches/server-unmapped-old/0001/0419-Pillager-patrol-spawn-settings-and-per-player-option.patch diff --git a/patches/server-unmapped/0420-Ensure-Entity-is-never-double-registered.patch b/patches/server-unmapped-old/0001/0420-Ensure-Entity-is-never-double-registered.patch similarity index 100% rename from patches/server-unmapped/0420-Ensure-Entity-is-never-double-registered.patch rename to patches/server-unmapped-old/0001/0420-Ensure-Entity-is-never-double-registered.patch diff --git a/patches/server-unmapped/0421-Fix-unregistering-entities-from-unloading-chunks.patch b/patches/server-unmapped-old/0001/0421-Fix-unregistering-entities-from-unloading-chunks.patch similarity index 100% rename from patches/server-unmapped/0421-Fix-unregistering-entities-from-unloading-chunks.patch rename to patches/server-unmapped-old/0001/0421-Fix-unregistering-entities-from-unloading-chunks.patch diff --git a/patches/server-unmapped/0422-Remote-Connections-shouldn-t-hold-up-shutdown.patch b/patches/server-unmapped-old/0001/0422-Remote-Connections-shouldn-t-hold-up-shutdown.patch similarity index 100% rename from patches/server-unmapped/0422-Remote-Connections-shouldn-t-hold-up-shutdown.patch rename to patches/server-unmapped-old/0001/0422-Remote-Connections-shouldn-t-hold-up-shutdown.patch diff --git a/patches/server-unmapped/0423-Do-not-allow-bees-to-load-chunks-for-beehives.patch b/patches/server-unmapped-old/0001/0423-Do-not-allow-bees-to-load-chunks-for-beehives.patch similarity index 100% rename from patches/server-unmapped/0423-Do-not-allow-bees-to-load-chunks-for-beehives.patch rename to patches/server-unmapped-old/0001/0423-Do-not-allow-bees-to-load-chunks-for-beehives.patch diff --git a/patches/server-unmapped/0424-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch b/patches/server-unmapped-old/0001/0424-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch similarity index 100% rename from patches/server-unmapped/0424-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch rename to patches/server-unmapped-old/0001/0424-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch diff --git a/patches/server-unmapped/0425-Optimize-Collision-to-not-load-chunks.patch b/patches/server-unmapped-old/0001/0425-Optimize-Collision-to-not-load-chunks.patch similarity index 100% rename from patches/server-unmapped/0425-Optimize-Collision-to-not-load-chunks.patch rename to patches/server-unmapped-old/0001/0425-Optimize-Collision-to-not-load-chunks.patch diff --git a/patches/server-unmapped/0426-Don-t-tick-dead-players.patch b/patches/server-unmapped-old/0001/0426-Don-t-tick-dead-players.patch similarity index 100% rename from patches/server-unmapped/0426-Don-t-tick-dead-players.patch rename to patches/server-unmapped-old/0001/0426-Don-t-tick-dead-players.patch diff --git a/patches/server-unmapped/0427-Dead-Player-s-shouldn-t-be-able-to-move.patch b/patches/server-unmapped-old/0001/0427-Dead-Player-s-shouldn-t-be-able-to-move.patch similarity index 100% rename from patches/server-unmapped/0427-Dead-Player-s-shouldn-t-be-able-to-move.patch rename to patches/server-unmapped-old/0001/0427-Dead-Player-s-shouldn-t-be-able-to-move.patch diff --git a/patches/server-unmapped/0428-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch b/patches/server-unmapped-old/0001/0428-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch similarity index 100% rename from patches/server-unmapped/0428-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch rename to patches/server-unmapped-old/0001/0428-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch diff --git a/patches/server-unmapped/0429-Increase-Light-Queue-Size.patch b/patches/server-unmapped-old/0001/0429-Increase-Light-Queue-Size.patch similarity index 100% rename from patches/server-unmapped/0429-Increase-Light-Queue-Size.patch rename to patches/server-unmapped-old/0001/0429-Increase-Light-Queue-Size.patch diff --git a/patches/server-unmapped/0430-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch b/patches/server-unmapped-old/0001/0430-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch similarity index 100% rename from patches/server-unmapped/0430-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch rename to patches/server-unmapped-old/0001/0430-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch diff --git a/patches/server-unmapped/0431-Don-t-move-existing-players-to-world-spawn.patch b/patches/server-unmapped-old/0001/0431-Don-t-move-existing-players-to-world-spawn.patch similarity index 100% rename from patches/server-unmapped/0431-Don-t-move-existing-players-to-world-spawn.patch rename to patches/server-unmapped-old/0001/0431-Don-t-move-existing-players-to-world-spawn.patch diff --git a/patches/server-unmapped/0432-Add-tick-times-API-and-mspt-command.patch b/patches/server-unmapped-old/0001/0432-Add-tick-times-API-and-mspt-command.patch similarity index 100% rename from patches/server-unmapped/0432-Add-tick-times-API-and-mspt-command.patch rename to patches/server-unmapped-old/0001/0432-Add-tick-times-API-and-mspt-command.patch diff --git a/patches/server-unmapped/0433-Expose-MinecraftServer-isRunning.patch b/patches/server-unmapped-old/0001/0433-Expose-MinecraftServer-isRunning.patch similarity index 100% rename from patches/server-unmapped/0433-Expose-MinecraftServer-isRunning.patch rename to patches/server-unmapped-old/0001/0433-Expose-MinecraftServer-isRunning.patch diff --git a/patches/server-unmapped/0434-Add-Raw-Byte-ItemStack-Serialization.patch b/patches/server-unmapped-old/0001/0434-Add-Raw-Byte-ItemStack-Serialization.patch similarity index 100% rename from patches/server-unmapped/0434-Add-Raw-Byte-ItemStack-Serialization.patch rename to patches/server-unmapped-old/0001/0434-Add-Raw-Byte-ItemStack-Serialization.patch diff --git a/patches/server-unmapped/0435-Remove-streams-from-Mob-AI-System.patch b/patches/server-unmapped-old/0001/0435-Remove-streams-from-Mob-AI-System.patch similarity index 100% rename from patches/server-unmapped/0435-Remove-streams-from-Mob-AI-System.patch rename to patches/server-unmapped-old/0001/0435-Remove-streams-from-Mob-AI-System.patch diff --git a/patches/server-unmapped/0436-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch b/patches/server-unmapped-old/0001/0436-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch similarity index 100% rename from patches/server-unmapped/0436-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch rename to patches/server-unmapped-old/0001/0436-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch diff --git a/patches/server-unmapped/0437-Async-command-map-building.patch b/patches/server-unmapped-old/0001/0437-Async-command-map-building.patch similarity index 100% rename from patches/server-unmapped/0437-Async-command-map-building.patch rename to patches/server-unmapped-old/0001/0437-Async-command-map-building.patch diff --git a/patches/server-unmapped/0438-Improved-Watchdog-Support.patch b/patches/server-unmapped-old/0001/0438-Improved-Watchdog-Support.patch similarity index 100% rename from patches/server-unmapped/0438-Improved-Watchdog-Support.patch rename to patches/server-unmapped-old/0001/0438-Improved-Watchdog-Support.patch diff --git a/patches/server-unmapped/0439-Optimize-Pathfinding.patch b/patches/server-unmapped-old/0001/0439-Optimize-Pathfinding.patch similarity index 100% rename from patches/server-unmapped/0439-Optimize-Pathfinding.patch rename to patches/server-unmapped-old/0001/0439-Optimize-Pathfinding.patch diff --git a/patches/server-unmapped/0440-Reduce-Either-Optional-allocation.patch b/patches/server-unmapped-old/0001/0440-Reduce-Either-Optional-allocation.patch similarity index 100% rename from patches/server-unmapped/0440-Reduce-Either-Optional-allocation.patch rename to patches/server-unmapped-old/0001/0440-Reduce-Either-Optional-allocation.patch diff --git a/patches/server-unmapped/0441-Remove-streams-from-PairedQueue.patch b/patches/server-unmapped-old/0001/0441-Remove-streams-from-PairedQueue.patch similarity index 100% rename from patches/server-unmapped/0441-Remove-streams-from-PairedQueue.patch rename to patches/server-unmapped-old/0001/0441-Remove-streams-from-PairedQueue.patch diff --git a/patches/server-unmapped/0442-Reduce-memory-footprint-of-NBTTagCompound.patch b/patches/server-unmapped-old/0001/0442-Reduce-memory-footprint-of-NBTTagCompound.patch similarity index 100% rename from patches/server-unmapped/0442-Reduce-memory-footprint-of-NBTTagCompound.patch rename to patches/server-unmapped-old/0001/0442-Reduce-memory-footprint-of-NBTTagCompound.patch diff --git a/patches/server-unmapped/0443-Prevent-opening-inventories-when-frozen.patch b/patches/server-unmapped-old/0001/0443-Prevent-opening-inventories-when-frozen.patch similarity index 100% rename from patches/server-unmapped/0443-Prevent-opening-inventories-when-frozen.patch rename to patches/server-unmapped-old/0001/0443-Prevent-opening-inventories-when-frozen.patch diff --git a/patches/server-unmapped/0444-Optimise-ArraySetSorted-removeIf.patch b/patches/server-unmapped-old/0001/0444-Optimise-ArraySetSorted-removeIf.patch similarity index 100% rename from patches/server-unmapped/0444-Optimise-ArraySetSorted-removeIf.patch rename to patches/server-unmapped-old/0001/0444-Optimise-ArraySetSorted-removeIf.patch diff --git a/patches/server-unmapped/0445-Don-t-run-entity-collision-code-if-not-needed.patch b/patches/server-unmapped-old/0001/0445-Don-t-run-entity-collision-code-if-not-needed.patch similarity index 100% rename from patches/server-unmapped/0445-Don-t-run-entity-collision-code-if-not-needed.patch rename to patches/server-unmapped-old/0001/0445-Don-t-run-entity-collision-code-if-not-needed.patch diff --git a/patches/server-unmapped/0446-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch b/patches/server-unmapped-old/0001/0446-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch similarity index 100% rename from patches/server-unmapped/0446-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch rename to patches/server-unmapped-old/0001/0446-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch diff --git a/patches/server-unmapped/0447-Restrict-vanilla-teleport-command-to-valid-locations.patch b/patches/server-unmapped-old/0001/0447-Restrict-vanilla-teleport-command-to-valid-locations.patch similarity index 100% rename from patches/server-unmapped/0447-Restrict-vanilla-teleport-command-to-valid-locations.patch rename to patches/server-unmapped-old/0001/0447-Restrict-vanilla-teleport-command-to-valid-locations.patch diff --git a/patches/server-unmapped/0448-Implement-Player-Client-Options-API.patch b/patches/server-unmapped-old/0001/0448-Implement-Player-Client-Options-API.patch similarity index 100% rename from patches/server-unmapped/0448-Implement-Player-Client-Options-API.patch rename to patches/server-unmapped-old/0001/0448-Implement-Player-Client-Options-API.patch diff --git a/patches/server-unmapped/0449-Fix-Chunk-Post-Processing-deadlock-risk.patch b/patches/server-unmapped-old/0001/0449-Fix-Chunk-Post-Processing-deadlock-risk.patch similarity index 100% rename from patches/server-unmapped/0449-Fix-Chunk-Post-Processing-deadlock-risk.patch rename to patches/server-unmapped-old/0001/0449-Fix-Chunk-Post-Processing-deadlock-risk.patch diff --git a/patches/server-unmapped/0450-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch b/patches/server-unmapped-old/0001/0450-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch similarity index 100% rename from patches/server-unmapped/0450-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch rename to patches/server-unmapped-old/0001/0450-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch diff --git a/patches/server-unmapped/0451-Broadcast-join-message-to-console.patch b/patches/server-unmapped-old/0001/0451-Broadcast-join-message-to-console.patch similarity index 100% rename from patches/server-unmapped/0451-Broadcast-join-message-to-console.patch rename to patches/server-unmapped-old/0001/0451-Broadcast-join-message-to-console.patch diff --git a/patches/server-unmapped/0452-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch b/patches/server-unmapped-old/0001/0452-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch similarity index 100% rename from patches/server-unmapped/0452-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch rename to patches/server-unmapped-old/0001/0452-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch diff --git a/patches/server-unmapped/0453-Load-Chunks-for-Login-Asynchronously.patch b/patches/server-unmapped-old/0001/0453-Load-Chunks-for-Login-Asynchronously.patch similarity index 100% rename from patches/server-unmapped/0453-Load-Chunks-for-Login-Asynchronously.patch rename to patches/server-unmapped-old/0001/0453-Load-Chunks-for-Login-Asynchronously.patch diff --git a/patches/server-unmapped/0454-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch b/patches/server-unmapped-old/0001/0454-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch similarity index 100% rename from patches/server-unmapped/0454-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch rename to patches/server-unmapped-old/0001/0454-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch diff --git a/patches/server-unmapped/0455-Add-PlayerAttackEntityCooldownResetEvent.patch b/patches/server-unmapped-old/0001/0455-Add-PlayerAttackEntityCooldownResetEvent.patch similarity index 100% rename from patches/server-unmapped/0455-Add-PlayerAttackEntityCooldownResetEvent.patch rename to patches/server-unmapped-old/0001/0455-Add-PlayerAttackEntityCooldownResetEvent.patch diff --git a/patches/server-unmapped/0456-Allow-multiple-callbacks-to-schedule-for-Callback-Ex.patch b/patches/server-unmapped-old/0001/0456-Allow-multiple-callbacks-to-schedule-for-Callback-Ex.patch similarity index 100% rename from patches/server-unmapped/0456-Allow-multiple-callbacks-to-schedule-for-Callback-Ex.patch rename to patches/server-unmapped-old/0001/0456-Allow-multiple-callbacks-to-schedule-for-Callback-Ex.patch diff --git a/patches/server-unmapped/0457-Don-t-fire-BlockFade-on-worldgen-threads.patch b/patches/server-unmapped-old/0001/0457-Don-t-fire-BlockFade-on-worldgen-threads.patch similarity index 100% rename from patches/server-unmapped/0457-Don-t-fire-BlockFade-on-worldgen-threads.patch rename to patches/server-unmapped-old/0001/0457-Don-t-fire-BlockFade-on-worldgen-threads.patch diff --git a/patches/server-unmapped/0458-Add-phantom-creative-and-insomniac-controls.patch b/patches/server-unmapped-old/0001/0458-Add-phantom-creative-and-insomniac-controls.patch similarity index 100% rename from patches/server-unmapped/0458-Add-phantom-creative-and-insomniac-controls.patch rename to patches/server-unmapped-old/0001/0458-Add-phantom-creative-and-insomniac-controls.patch diff --git a/patches/server-unmapped/0459-Fix-numerous-item-duplication-issues-and-teleport-is.patch b/patches/server-unmapped-old/0001/0459-Fix-numerous-item-duplication-issues-and-teleport-is.patch similarity index 100% rename from patches/server-unmapped/0459-Fix-numerous-item-duplication-issues-and-teleport-is.patch rename to patches/server-unmapped-old/0001/0459-Fix-numerous-item-duplication-issues-and-teleport-is.patch diff --git a/patches/server-unmapped/0460-Implement-Brigadier-Mojang-API.patch b/patches/server-unmapped-old/0001/0460-Implement-Brigadier-Mojang-API.patch similarity index 100% rename from patches/server-unmapped/0460-Implement-Brigadier-Mojang-API.patch rename to patches/server-unmapped-old/0001/0460-Implement-Brigadier-Mojang-API.patch diff --git a/patches/server-unmapped/0461-Villager-Restocks-API.patch b/patches/server-unmapped-old/0001/0461-Villager-Restocks-API.patch similarity index 100% rename from patches/server-unmapped/0461-Villager-Restocks-API.patch rename to patches/server-unmapped-old/0001/0461-Villager-Restocks-API.patch diff --git a/patches/server-unmapped/0462-Validate-PickItem-Packet-and-kick-for-invalid.patch b/patches/server-unmapped-old/0001/0462-Validate-PickItem-Packet-and-kick-for-invalid.patch similarity index 100% rename from patches/server-unmapped/0462-Validate-PickItem-Packet-and-kick-for-invalid.patch rename to patches/server-unmapped-old/0001/0462-Validate-PickItem-Packet-and-kick-for-invalid.patch diff --git a/patches/server-unmapped/0463-Expose-game-version.patch b/patches/server-unmapped-old/0001/0463-Expose-game-version.patch similarity index 100% rename from patches/server-unmapped/0463-Expose-game-version.patch rename to patches/server-unmapped-old/0001/0463-Expose-game-version.patch diff --git a/patches/server-unmapped/0464-Optimize-Voxel-Shape-Merging.patch b/patches/server-unmapped-old/0001/0464-Optimize-Voxel-Shape-Merging.patch similarity index 100% rename from patches/server-unmapped/0464-Optimize-Voxel-Shape-Merging.patch rename to patches/server-unmapped-old/0001/0464-Optimize-Voxel-Shape-Merging.patch diff --git a/patches/server-unmapped/0465-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch b/patches/server-unmapped-old/0001/0465-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch similarity index 100% rename from patches/server-unmapped/0465-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch rename to patches/server-unmapped-old/0001/0465-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch diff --git a/patches/server-unmapped/0466-Implement-Mob-Goal-API.patch b/patches/server-unmapped-old/0001/0466-Implement-Mob-Goal-API.patch similarity index 100% rename from patches/server-unmapped/0466-Implement-Mob-Goal-API.patch rename to patches/server-unmapped-old/0001/0466-Implement-Mob-Goal-API.patch diff --git a/patches/server-unmapped/0467-Use-distance-map-to-optimise-entity-tracker.patch b/patches/server-unmapped-old/0001/0467-Use-distance-map-to-optimise-entity-tracker.patch similarity index 100% rename from patches/server-unmapped/0467-Use-distance-map-to-optimise-entity-tracker.patch rename to patches/server-unmapped-old/0001/0467-Use-distance-map-to-optimise-entity-tracker.patch diff --git a/patches/server-unmapped/0468-Optimize-isOutsideRange-to-use-distance-maps.patch b/patches/server-unmapped-old/0001/0468-Optimize-isOutsideRange-to-use-distance-maps.patch similarity index 100% rename from patches/server-unmapped/0468-Optimize-isOutsideRange-to-use-distance-maps.patch rename to patches/server-unmapped-old/0001/0468-Optimize-isOutsideRange-to-use-distance-maps.patch diff --git a/patches/server-unmapped/0469-Stop-copy-on-write-operations-for-updating-light-dat.patch b/patches/server-unmapped-old/0001/0469-Stop-copy-on-write-operations-for-updating-light-dat.patch similarity index 100% rename from patches/server-unmapped/0469-Stop-copy-on-write-operations-for-updating-light-dat.patch rename to patches/server-unmapped-old/0001/0469-Stop-copy-on-write-operations-for-updating-light-dat.patch diff --git a/patches/server-unmapped/0470-No-Tick-view-distance-implementation.patch b/patches/server-unmapped-old/0001/0470-No-Tick-view-distance-implementation.patch similarity index 100% rename from patches/server-unmapped/0470-No-Tick-view-distance-implementation.patch rename to patches/server-unmapped-old/0001/0470-No-Tick-view-distance-implementation.patch diff --git a/patches/server-unmapped/0471-Add-villager-reputation-API.patch b/patches/server-unmapped-old/0001/0471-Add-villager-reputation-API.patch similarity index 100% rename from patches/server-unmapped/0471-Add-villager-reputation-API.patch rename to patches/server-unmapped-old/0001/0471-Add-villager-reputation-API.patch diff --git a/patches/server-unmapped/0472-Fix-Light-Command.patch b/patches/server-unmapped-old/0001/0472-Fix-Light-Command.patch similarity index 100% rename from patches/server-unmapped/0472-Fix-Light-Command.patch rename to patches/server-unmapped-old/0001/0472-Fix-Light-Command.patch diff --git a/patches/server-unmapped/0473-Fix-PotionEffect-ignores-icon-flag.patch b/patches/server-unmapped-old/0001/0473-Fix-PotionEffect-ignores-icon-flag.patch similarity index 100% rename from patches/server-unmapped/0473-Fix-PotionEffect-ignores-icon-flag.patch rename to patches/server-unmapped-old/0001/0473-Fix-PotionEffect-ignores-icon-flag.patch diff --git a/patches/server-unmapped/0474-Optimize-brigadier-child-sorting-performance.patch b/patches/server-unmapped-old/0001/0474-Optimize-brigadier-child-sorting-performance.patch similarity index 100% rename from patches/server-unmapped/0474-Optimize-brigadier-child-sorting-performance.patch rename to patches/server-unmapped-old/0001/0474-Optimize-brigadier-child-sorting-performance.patch diff --git a/patches/server-unmapped/0475-Potential-bed-API.patch b/patches/server-unmapped-old/0001/0475-Potential-bed-API.patch similarity index 100% rename from patches/server-unmapped/0475-Potential-bed-API.patch rename to patches/server-unmapped-old/0001/0475-Potential-bed-API.patch diff --git a/patches/server-unmapped/0476-Wait-for-Async-Tasks-during-shutdown.patch b/patches/server-unmapped-old/0001/0476-Wait-for-Async-Tasks-during-shutdown.patch similarity index 100% rename from patches/server-unmapped/0476-Wait-for-Async-Tasks-during-shutdown.patch rename to patches/server-unmapped-old/0001/0476-Wait-for-Async-Tasks-during-shutdown.patch diff --git a/patches/server-unmapped/0477-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch b/patches/server-unmapped-old/0001/0477-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch similarity index 100% rename from patches/server-unmapped/0477-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch rename to patches/server-unmapped-old/0001/0477-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch diff --git a/patches/server-unmapped/0478-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch b/patches/server-unmapped-old/0001/0478-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch similarity index 100% rename from patches/server-unmapped/0478-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch rename to patches/server-unmapped-old/0001/0478-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch diff --git a/patches/server-unmapped/0479-Optimize-NibbleArray-to-use-pooled-buffers.patch b/patches/server-unmapped-old/0001/0479-Optimize-NibbleArray-to-use-pooled-buffers.patch similarity index 100% rename from patches/server-unmapped/0479-Optimize-NibbleArray-to-use-pooled-buffers.patch rename to patches/server-unmapped-old/0001/0479-Optimize-NibbleArray-to-use-pooled-buffers.patch diff --git a/patches/server-unmapped/0480-Reduce-MutableInt-allocations-from-light-engine.patch b/patches/server-unmapped-old/0001/0480-Reduce-MutableInt-allocations-from-light-engine.patch similarity index 100% rename from patches/server-unmapped/0480-Reduce-MutableInt-allocations-from-light-engine.patch rename to patches/server-unmapped-old/0001/0480-Reduce-MutableInt-allocations-from-light-engine.patch diff --git a/patches/server-unmapped/0481-Reduce-allocation-of-Vec3D-by-entity-tracker.patch b/patches/server-unmapped-old/0001/0481-Reduce-allocation-of-Vec3D-by-entity-tracker.patch similarity index 100% rename from patches/server-unmapped/0481-Reduce-allocation-of-Vec3D-by-entity-tracker.patch rename to patches/server-unmapped-old/0001/0481-Reduce-allocation-of-Vec3D-by-entity-tracker.patch diff --git a/patches/server-unmapped/0482-Ensure-safe-gateway-teleport.patch b/patches/server-unmapped-old/0001/0482-Ensure-safe-gateway-teleport.patch similarity index 100% rename from patches/server-unmapped/0482-Ensure-safe-gateway-teleport.patch rename to patches/server-unmapped-old/0001/0482-Ensure-safe-gateway-teleport.patch diff --git a/patches/server-unmapped/0483-Add-option-for-console-having-all-permissions.patch b/patches/server-unmapped-old/0001/0483-Add-option-for-console-having-all-permissions.patch similarity index 100% rename from patches/server-unmapped/0483-Add-option-for-console-having-all-permissions.patch rename to patches/server-unmapped-old/0001/0483-Add-option-for-console-having-all-permissions.patch diff --git a/patches/server-unmapped/0484-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch b/patches/server-unmapped-old/0001/0484-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch similarity index 100% rename from patches/server-unmapped/0484-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch rename to patches/server-unmapped-old/0001/0484-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch diff --git a/patches/server-unmapped/0485-Workaround-for-Client-Lag-Spikes-MC-162253.patch b/patches/server-unmapped-old/0001/0485-Workaround-for-Client-Lag-Spikes-MC-162253.patch similarity index 100% rename from patches/server-unmapped/0485-Workaround-for-Client-Lag-Spikes-MC-162253.patch rename to patches/server-unmapped-old/0001/0485-Workaround-for-Client-Lag-Spikes-MC-162253.patch diff --git a/patches/server-unmapped/0486-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/patches/server-unmapped-old/0001/0486-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch similarity index 100% rename from patches/server-unmapped/0486-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch rename to patches/server-unmapped-old/0001/0486-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch diff --git a/patches/server-unmapped/0487-Optimize-sending-packets-to-nearby-locations-sounds-.patch b/patches/server-unmapped-old/0001/0487-Optimize-sending-packets-to-nearby-locations-sounds-.patch similarity index 100% rename from patches/server-unmapped/0487-Optimize-sending-packets-to-nearby-locations-sounds-.patch rename to patches/server-unmapped-old/0001/0487-Optimize-sending-packets-to-nearby-locations-sounds-.patch diff --git a/patches/server-unmapped/0488-Improve-Chunk-Status-Transition-Speed.patch b/patches/server-unmapped-old/0001/0488-Improve-Chunk-Status-Transition-Speed.patch similarity index 100% rename from patches/server-unmapped/0488-Improve-Chunk-Status-Transition-Speed.patch rename to patches/server-unmapped-old/0001/0488-Improve-Chunk-Status-Transition-Speed.patch diff --git a/patches/server-unmapped/0489-Fix-villager-trading-demand-MC-163962.patch b/patches/server-unmapped-old/0001/0489-Fix-villager-trading-demand-MC-163962.patch similarity index 100% rename from patches/server-unmapped/0489-Fix-villager-trading-demand-MC-163962.patch rename to patches/server-unmapped-old/0001/0489-Fix-villager-trading-demand-MC-163962.patch diff --git a/patches/server-unmapped/0490-Maps-shouldn-t-load-chunks.patch b/patches/server-unmapped-old/0001/0490-Maps-shouldn-t-load-chunks.patch similarity index 100% rename from patches/server-unmapped/0490-Maps-shouldn-t-load-chunks.patch rename to patches/server-unmapped-old/0001/0490-Maps-shouldn-t-load-chunks.patch diff --git a/patches/server-unmapped/0491-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch b/patches/server-unmapped-old/0001/0491-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch similarity index 100% rename from patches/server-unmapped/0491-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch rename to patches/server-unmapped-old/0001/0491-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch diff --git a/patches/server-unmapped/0492-Optimize-Bit-Operations-by-inlining.patch b/patches/server-unmapped-old/0001/0492-Optimize-Bit-Operations-by-inlining.patch similarity index 100% rename from patches/server-unmapped/0492-Optimize-Bit-Operations-by-inlining.patch rename to patches/server-unmapped-old/0001/0492-Optimize-Bit-Operations-by-inlining.patch diff --git a/patches/server-unmapped/0493-Optimize-Light-Engine.patch b/patches/server-unmapped-old/0001/0493-Optimize-Light-Engine.patch similarity index 100% rename from patches/server-unmapped/0493-Optimize-Light-Engine.patch rename to patches/server-unmapped-old/0001/0493-Optimize-Light-Engine.patch diff --git a/patches/server-unmapped/0494-Delay-Chunk-Unloads-based-on-Player-Movement.patch b/patches/server-unmapped-old/0001/0494-Delay-Chunk-Unloads-based-on-Player-Movement.patch similarity index 100% rename from patches/server-unmapped/0494-Delay-Chunk-Unloads-based-on-Player-Movement.patch rename to patches/server-unmapped-old/0001/0494-Delay-Chunk-Unloads-based-on-Player-Movement.patch diff --git a/patches/server-unmapped/0495-Add-Plugin-Tickets-to-API-Chunk-Methods.patch b/patches/server-unmapped-old/0001/0495-Add-Plugin-Tickets-to-API-Chunk-Methods.patch similarity index 100% rename from patches/server-unmapped/0495-Add-Plugin-Tickets-to-API-Chunk-Methods.patch rename to patches/server-unmapped-old/0001/0495-Add-Plugin-Tickets-to-API-Chunk-Methods.patch diff --git a/patches/server-unmapped/0496-Fix-missing-chunks-due-to-integer-overflow.patch b/patches/server-unmapped-old/0001/0496-Fix-missing-chunks-due-to-integer-overflow.patch similarity index 100% rename from patches/server-unmapped/0496-Fix-missing-chunks-due-to-integer-overflow.patch rename to patches/server-unmapped-old/0001/0496-Fix-missing-chunks-due-to-integer-overflow.patch diff --git a/patches/server-unmapped/0497-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch b/patches/server-unmapped-old/0001/0497-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch similarity index 100% rename from patches/server-unmapped/0497-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch rename to patches/server-unmapped-old/0001/0497-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch diff --git a/patches/server-unmapped/0498-Fix-piston-physics-inconsistency-MC-188840.patch b/patches/server-unmapped-old/0001/0498-Fix-piston-physics-inconsistency-MC-188840.patch similarity index 100% rename from patches/server-unmapped/0498-Fix-piston-physics-inconsistency-MC-188840.patch rename to patches/server-unmapped-old/0001/0498-Fix-piston-physics-inconsistency-MC-188840.patch diff --git a/patches/server-unmapped/0499-Fix-sand-duping.patch b/patches/server-unmapped-old/0001/0499-Fix-sand-duping.patch similarity index 100% rename from patches/server-unmapped/0499-Fix-sand-duping.patch rename to patches/server-unmapped-old/0001/0499-Fix-sand-duping.patch diff --git a/patches/server-unmapped/0500-Prevent-position-desync-in-playerconnection-causing-.patch b/patches/server-unmapped-old/0001/0500-Prevent-position-desync-in-playerconnection-causing-.patch similarity index 100% rename from patches/server-unmapped/0500-Prevent-position-desync-in-playerconnection-causing-.patch rename to patches/server-unmapped-old/0001/0500-Prevent-position-desync-in-playerconnection-causing-.patch diff --git a/patches/server-unmapped/0501-Fix-enderdragon-exp-dupe.patch b/patches/server-unmapped-old/0001/0501-Fix-enderdragon-exp-dupe.patch similarity index 100% rename from patches/server-unmapped/0501-Fix-enderdragon-exp-dupe.patch rename to patches/server-unmapped-old/0001/0501-Fix-enderdragon-exp-dupe.patch diff --git a/patches/server-unmapped/0502-Inventory-getHolder-method-without-block-snapshot.patch b/patches/server-unmapped-old/0001/0502-Inventory-getHolder-method-without-block-snapshot.patch similarity index 100% rename from patches/server-unmapped/0502-Inventory-getHolder-method-without-block-snapshot.patch rename to patches/server-unmapped-old/0001/0502-Inventory-getHolder-method-without-block-snapshot.patch diff --git a/patches/server-unmapped/0503-Expose-Arrow-getItemStack.patch b/patches/server-unmapped-old/0001/0503-Expose-Arrow-getItemStack.patch similarity index 100% rename from patches/server-unmapped/0503-Expose-Arrow-getItemStack.patch rename to patches/server-unmapped-old/0001/0503-Expose-Arrow-getItemStack.patch diff --git a/patches/server-unmapped/0504-Add-and-implement-PlayerRecipeBookClickEvent.patch b/patches/server-unmapped-old/0001/0504-Add-and-implement-PlayerRecipeBookClickEvent.patch similarity index 100% rename from patches/server-unmapped/0504-Add-and-implement-PlayerRecipeBookClickEvent.patch rename to patches/server-unmapped-old/0001/0504-Add-and-implement-PlayerRecipeBookClickEvent.patch diff --git a/patches/server-unmapped/0505-Hide-sync-chunk-writes-behind-flag.patch b/patches/server-unmapped-old/0001/0505-Hide-sync-chunk-writes-behind-flag.patch similarity index 100% rename from patches/server-unmapped/0505-Hide-sync-chunk-writes-behind-flag.patch rename to patches/server-unmapped-old/0001/0505-Hide-sync-chunk-writes-behind-flag.patch diff --git a/patches/server-unmapped/0506-Limit-lightning-strike-effect-distance.patch b/patches/server-unmapped-old/0001/0506-Limit-lightning-strike-effect-distance.patch similarity index 100% rename from patches/server-unmapped/0506-Limit-lightning-strike-effect-distance.patch rename to patches/server-unmapped-old/0001/0506-Limit-lightning-strike-effect-distance.patch diff --git a/patches/server-unmapped/0507-Add-permission-for-command-blocks.patch b/patches/server-unmapped-old/0001/0507-Add-permission-for-command-blocks.patch similarity index 100% rename from patches/server-unmapped/0507-Add-permission-for-command-blocks.patch rename to patches/server-unmapped-old/0001/0507-Add-permission-for-command-blocks.patch diff --git a/patches/server-unmapped/0508-Ensure-Entity-AABB-s-are-never-invalid.patch b/patches/server-unmapped-old/0001/0508-Ensure-Entity-AABB-s-are-never-invalid.patch similarity index 100% rename from patches/server-unmapped/0508-Ensure-Entity-AABB-s-are-never-invalid.patch rename to patches/server-unmapped-old/0001/0508-Ensure-Entity-AABB-s-are-never-invalid.patch diff --git a/patches/server-unmapped/0509-Optimize-WorldBorder-collision-checks-and-air.patch b/patches/server-unmapped-old/0001/0509-Optimize-WorldBorder-collision-checks-and-air.patch similarity index 100% rename from patches/server-unmapped/0509-Optimize-WorldBorder-collision-checks-and-air.patch rename to patches/server-unmapped-old/0001/0509-Optimize-WorldBorder-collision-checks-and-air.patch diff --git a/patches/server-unmapped/0510-Fix-Per-World-Difficulty-Remembering-Difficulty.patch b/patches/server-unmapped-old/0001/0510-Fix-Per-World-Difficulty-Remembering-Difficulty.patch similarity index 100% rename from patches/server-unmapped/0510-Fix-Per-World-Difficulty-Remembering-Difficulty.patch rename to patches/server-unmapped-old/0001/0510-Fix-Per-World-Difficulty-Remembering-Difficulty.patch diff --git a/patches/server-unmapped/0511-Paper-dumpitem-command.patch b/patches/server-unmapped-old/0001/0511-Paper-dumpitem-command.patch similarity index 100% rename from patches/server-unmapped/0511-Paper-dumpitem-command.patch rename to patches/server-unmapped-old/0001/0511-Paper-dumpitem-command.patch diff --git a/patches/server-unmapped/0512-Don-t-allow-null-UUID-s-for-chat.patch b/patches/server-unmapped-old/0001/0512-Don-t-allow-null-UUID-s-for-chat.patch similarity index 100% rename from patches/server-unmapped/0512-Don-t-allow-null-UUID-s-for-chat.patch rename to patches/server-unmapped-old/0001/0512-Don-t-allow-null-UUID-s-for-chat.patch diff --git a/patches/server-unmapped/0513-Improve-Legacy-Component-serialization-size.patch b/patches/server-unmapped-old/0001/0513-Improve-Legacy-Component-serialization-size.patch similarity index 100% rename from patches/server-unmapped/0513-Improve-Legacy-Component-serialization-size.patch rename to patches/server-unmapped-old/0001/0513-Improve-Legacy-Component-serialization-size.patch diff --git a/patches/server-unmapped/0514-Support-old-UUID-format-for-NBT.patch b/patches/server-unmapped-old/0001/0514-Support-old-UUID-format-for-NBT.patch similarity index 100% rename from patches/server-unmapped/0514-Support-old-UUID-format-for-NBT.patch rename to patches/server-unmapped-old/0001/0514-Support-old-UUID-format-for-NBT.patch diff --git a/patches/server-unmapped/0515-Clean-up-duplicated-GameProfile-Properties.patch b/patches/server-unmapped-old/0001/0515-Clean-up-duplicated-GameProfile-Properties.patch similarity index 100% rename from patches/server-unmapped/0515-Clean-up-duplicated-GameProfile-Properties.patch rename to patches/server-unmapped-old/0001/0515-Clean-up-duplicated-GameProfile-Properties.patch diff --git a/patches/server-unmapped/0516-Convert-legacy-attributes-in-Item-Meta.patch b/patches/server-unmapped-old/0001/0516-Convert-legacy-attributes-in-Item-Meta.patch similarity index 100% rename from patches/server-unmapped/0516-Convert-legacy-attributes-in-Item-Meta.patch rename to patches/server-unmapped-old/0001/0516-Convert-legacy-attributes-in-Item-Meta.patch diff --git a/patches/server-unmapped/0517-Remove-some-streams-from-structures.patch b/patches/server-unmapped-old/0001/0517-Remove-some-streams-from-structures.patch similarity index 100% rename from patches/server-unmapped/0517-Remove-some-streams-from-structures.patch rename to patches/server-unmapped-old/0001/0517-Remove-some-streams-from-structures.patch diff --git a/patches/server-unmapped/0518-Remove-streams-from-classes-related-villager-gossip.patch b/patches/server-unmapped-old/0001/0518-Remove-streams-from-classes-related-villager-gossip.patch similarity index 100% rename from patches/server-unmapped/0518-Remove-streams-from-classes-related-villager-gossip.patch rename to patches/server-unmapped-old/0001/0518-Remove-streams-from-classes-related-villager-gossip.patch diff --git a/patches/server-unmapped/0519-Support-components-in-ItemMeta.patch b/patches/server-unmapped-old/0001/0519-Support-components-in-ItemMeta.patch similarity index 100% rename from patches/server-unmapped/0519-Support-components-in-ItemMeta.patch rename to patches/server-unmapped-old/0001/0519-Support-components-in-ItemMeta.patch diff --git a/patches/server-unmapped/0520-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch b/patches/server-unmapped-old/0001/0520-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch similarity index 100% rename from patches/server-unmapped/0520-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch rename to patches/server-unmapped-old/0001/0520-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch diff --git a/patches/server-unmapped/0521-Add-entity-liquid-API.patch b/patches/server-unmapped-old/0001/0521-Add-entity-liquid-API.patch similarity index 100% rename from patches/server-unmapped/0521-Add-entity-liquid-API.patch rename to patches/server-unmapped-old/0001/0521-Add-entity-liquid-API.patch diff --git a/patches/server-unmapped/0522-Update-itemstack-legacy-name-and-lore.patch b/patches/server-unmapped-old/0001/0522-Update-itemstack-legacy-name-and-lore.patch similarity index 100% rename from patches/server-unmapped/0522-Update-itemstack-legacy-name-and-lore.patch rename to patches/server-unmapped-old/0001/0522-Update-itemstack-legacy-name-and-lore.patch diff --git a/patches/server-unmapped/0523-Spawn-player-in-correct-world-on-login.patch b/patches/server-unmapped-old/0001/0523-Spawn-player-in-correct-world-on-login.patch similarity index 100% rename from patches/server-unmapped/0523-Spawn-player-in-correct-world-on-login.patch rename to patches/server-unmapped-old/0001/0523-Spawn-player-in-correct-world-on-login.patch diff --git a/patches/server-unmapped/0524-Add-PrepareResultEvent.patch b/patches/server-unmapped-old/0001/0524-Add-PrepareResultEvent.patch similarity index 100% rename from patches/server-unmapped/0524-Add-PrepareResultEvent.patch rename to patches/server-unmapped-old/0001/0524-Add-PrepareResultEvent.patch diff --git a/patches/server-unmapped/0525-Allow-delegation-to-vanilla-chunk-gen.patch b/patches/server-unmapped-old/0001/0525-Allow-delegation-to-vanilla-chunk-gen.patch similarity index 100% rename from patches/server-unmapped/0525-Allow-delegation-to-vanilla-chunk-gen.patch rename to patches/server-unmapped-old/0001/0525-Allow-delegation-to-vanilla-chunk-gen.patch diff --git a/patches/server-unmapped/0526-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch b/patches/server-unmapped-old/0001/0526-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch similarity index 100% rename from patches/server-unmapped/0526-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch rename to patches/server-unmapped-old/0001/0526-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch diff --git a/patches/server-unmapped/0527-Optimize-NetworkManager-Exception-Handling.patch b/patches/server-unmapped-old/0001/0527-Optimize-NetworkManager-Exception-Handling.patch similarity index 100% rename from patches/server-unmapped/0527-Optimize-NetworkManager-Exception-Handling.patch rename to patches/server-unmapped-old/0001/0527-Optimize-NetworkManager-Exception-Handling.patch diff --git a/patches/server-unmapped/0528-Fix-Concurrency-issue-in-WeightedList.patch b/patches/server-unmapped-old/0001/0528-Fix-Concurrency-issue-in-WeightedList.patch similarity index 100% rename from patches/server-unmapped/0528-Fix-Concurrency-issue-in-WeightedList.patch rename to patches/server-unmapped-old/0001/0528-Fix-Concurrency-issue-in-WeightedList.patch diff --git a/patches/server-unmapped/0529-Optimize-the-advancement-data-player-iteration-to-be.patch b/patches/server-unmapped-old/0001/0529-Optimize-the-advancement-data-player-iteration-to-be.patch similarity index 100% rename from patches/server-unmapped/0529-Optimize-the-advancement-data-player-iteration-to-be.patch rename to patches/server-unmapped-old/0001/0529-Optimize-the-advancement-data-player-iteration-to-be.patch diff --git a/patches/server-unmapped/0530-Fix-arrows-never-despawning-MC-125757.patch b/patches/server-unmapped-old/0001/0530-Fix-arrows-never-despawning-MC-125757.patch similarity index 100% rename from patches/server-unmapped/0530-Fix-arrows-never-despawning-MC-125757.patch rename to patches/server-unmapped-old/0001/0530-Fix-arrows-never-despawning-MC-125757.patch diff --git a/patches/server-unmapped/0531-Thread-Safe-Vanilla-Command-permission-checking.patch b/patches/server-unmapped-old/0001/0531-Thread-Safe-Vanilla-Command-permission-checking.patch similarity index 100% rename from patches/server-unmapped/0531-Thread-Safe-Vanilla-Command-permission-checking.patch rename to patches/server-unmapped-old/0001/0531-Thread-Safe-Vanilla-Command-permission-checking.patch diff --git a/patches/server-unmapped/0532-Move-range-check-for-block-placing-up.patch b/patches/server-unmapped-old/0001/0532-Move-range-check-for-block-placing-up.patch similarity index 100% rename from patches/server-unmapped/0532-Move-range-check-for-block-placing-up.patch rename to patches/server-unmapped-old/0001/0532-Move-range-check-for-block-placing-up.patch diff --git a/patches/server-unmapped/0533-Fix-SPIGOT-5989.patch b/patches/server-unmapped-old/0001/0533-Fix-SPIGOT-5989.patch similarity index 100% rename from patches/server-unmapped/0533-Fix-SPIGOT-5989.patch rename to patches/server-unmapped-old/0001/0533-Fix-SPIGOT-5989.patch diff --git a/patches/server-unmapped/0534-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch b/patches/server-unmapped-old/0001/0534-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch similarity index 100% rename from patches/server-unmapped/0534-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch rename to patches/server-unmapped-old/0001/0534-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch diff --git a/patches/server-unmapped/0535-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch b/patches/server-unmapped-old/0001/0535-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch similarity index 100% rename from patches/server-unmapped/0535-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch rename to patches/server-unmapped-old/0001/0535-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch diff --git a/patches/server-unmapped/0536-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch b/patches/server-unmapped-old/0001/0536-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch similarity index 100% rename from patches/server-unmapped/0536-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch rename to patches/server-unmapped-old/0001/0536-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch diff --git a/patches/server-unmapped/0537-Add-missing-strikeLighting-call-to-World-spigot-stri.patch b/patches/server-unmapped-old/0001/0537-Add-missing-strikeLighting-call-to-World-spigot-stri.patch similarity index 100% rename from patches/server-unmapped/0537-Add-missing-strikeLighting-call-to-World-spigot-stri.patch rename to patches/server-unmapped-old/0001/0537-Add-missing-strikeLighting-call-to-World-spigot-stri.patch diff --git a/patches/server-unmapped/0538-Fix-some-rails-connecting-improperly.patch b/patches/server-unmapped-old/0001/0538-Fix-some-rails-connecting-improperly.patch similarity index 100% rename from patches/server-unmapped/0538-Fix-some-rails-connecting-improperly.patch rename to patches/server-unmapped-old/0001/0538-Fix-some-rails-connecting-improperly.patch diff --git a/patches/server-unmapped/0539-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch b/patches/server-unmapped-old/0001/0539-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch similarity index 100% rename from patches/server-unmapped/0539-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch rename to patches/server-unmapped-old/0001/0539-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch diff --git a/patches/server-unmapped/0540-Incremental-player-saving.patch b/patches/server-unmapped-old/0001/0540-Incremental-player-saving.patch similarity index 100% rename from patches/server-unmapped/0540-Incremental-player-saving.patch rename to patches/server-unmapped-old/0001/0540-Incremental-player-saving.patch diff --git a/patches/server-unmapped/0541-Import-fastutil-classes.patch b/patches/server-unmapped-old/0001/0541-Import-fastutil-classes.patch similarity index 100% rename from patches/server-unmapped/0541-Import-fastutil-classes.patch rename to patches/server-unmapped-old/0001/0541-Import-fastutil-classes.patch diff --git a/patches/server-unmapped/0542-Don-t-mark-null-chunk-sections-for-block-updates.patch b/patches/server-unmapped-old/0001/0542-Don-t-mark-null-chunk-sections-for-block-updates.patch similarity index 100% rename from patches/server-unmapped/0542-Don-t-mark-null-chunk-sections-for-block-updates.patch rename to patches/server-unmapped-old/0001/0542-Don-t-mark-null-chunk-sections-for-block-updates.patch diff --git a/patches/server-unmapped/0543-Remove-armour-stand-double-add-to-world.patch b/patches/server-unmapped-old/0001/0543-Remove-armour-stand-double-add-to-world.patch similarity index 100% rename from patches/server-unmapped/0543-Remove-armour-stand-double-add-to-world.patch rename to patches/server-unmapped-old/0001/0543-Remove-armour-stand-double-add-to-world.patch diff --git a/patches/server-unmapped/0544-Fix-MC-187716-Use-configured-height.patch b/patches/server-unmapped-old/0001/0544-Fix-MC-187716-Use-configured-height.patch similarity index 100% rename from patches/server-unmapped/0544-Fix-MC-187716-Use-configured-height.patch rename to patches/server-unmapped-old/0001/0544-Fix-MC-187716-Use-configured-height.patch diff --git a/patches/server-unmapped/0545-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch b/patches/server-unmapped-old/0001/0545-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch similarity index 100% rename from patches/server-unmapped/0545-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch rename to patches/server-unmapped-old/0001/0545-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch diff --git a/patches/server-unmapped/0546-Do-not-let-the-server-load-chunks-from-newer-version.patch b/patches/server-unmapped-old/0001/0546-Do-not-let-the-server-load-chunks-from-newer-version.patch similarity index 100% rename from patches/server-unmapped/0546-Do-not-let-the-server-load-chunks-from-newer-version.patch rename to patches/server-unmapped-old/0001/0546-Do-not-let-the-server-load-chunks-from-newer-version.patch diff --git a/patches/server-unmapped/0547-Brand-support.patch b/patches/server-unmapped-old/0001/0547-Brand-support.patch similarity index 100% rename from patches/server-unmapped/0547-Brand-support.patch rename to patches/server-unmapped-old/0001/0547-Brand-support.patch diff --git a/patches/server-unmapped/0548-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch b/patches/server-unmapped-old/0001/0548-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch similarity index 100% rename from patches/server-unmapped/0548-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch rename to patches/server-unmapped-old/0001/0548-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch diff --git a/patches/server-unmapped/0549-Fix-MC-197271.patch b/patches/server-unmapped-old/0001/0549-Fix-MC-197271.patch similarity index 100% rename from patches/server-unmapped/0549-Fix-MC-197271.patch rename to patches/server-unmapped-old/0001/0549-Fix-MC-197271.patch diff --git a/patches/server-unmapped/0550-MC-197883-Bandaid-decode-issue.patch b/patches/server-unmapped-old/0001/0550-MC-197883-Bandaid-decode-issue.patch similarity index 100% rename from patches/server-unmapped/0550-MC-197883-Bandaid-decode-issue.patch rename to patches/server-unmapped-old/0001/0550-MC-197883-Bandaid-decode-issue.patch diff --git a/patches/server-unmapped/0551-Add-setMaxPlayers-API.patch b/patches/server-unmapped-old/0001/0551-Add-setMaxPlayers-API.patch similarity index 100% rename from patches/server-unmapped/0551-Add-setMaxPlayers-API.patch rename to patches/server-unmapped-old/0001/0551-Add-setMaxPlayers-API.patch diff --git a/patches/server-unmapped/0552-Add-playPickupItemAnimation-to-LivingEntity.patch b/patches/server-unmapped-old/0001/0552-Add-playPickupItemAnimation-to-LivingEntity.patch similarity index 100% rename from patches/server-unmapped/0552-Add-playPickupItemAnimation-to-LivingEntity.patch rename to patches/server-unmapped-old/0001/0552-Add-playPickupItemAnimation-to-LivingEntity.patch diff --git a/patches/server-unmapped/0553-Don-t-require-FACING-data.patch b/patches/server-unmapped-old/0001/0553-Don-t-require-FACING-data.patch similarity index 100% rename from patches/server-unmapped/0553-Don-t-require-FACING-data.patch rename to patches/server-unmapped-old/0001/0553-Don-t-require-FACING-data.patch diff --git a/patches/server-unmapped/0554-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch b/patches/server-unmapped-old/0001/0554-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch similarity index 100% rename from patches/server-unmapped/0554-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch rename to patches/server-unmapped-old/0001/0554-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch diff --git a/patches/server-unmapped/0555-Add-moon-phase-API.patch b/patches/server-unmapped-old/0001/0555-Add-moon-phase-API.patch similarity index 100% rename from patches/server-unmapped/0555-Add-moon-phase-API.patch rename to patches/server-unmapped-old/0001/0555-Add-moon-phase-API.patch diff --git a/patches/server-unmapped/0556-Prevent-headless-pistons-from-being-created.patch b/patches/server-unmapped-old/0001/0556-Prevent-headless-pistons-from-being-created.patch similarity index 100% rename from patches/server-unmapped/0556-Prevent-headless-pistons-from-being-created.patch rename to patches/server-unmapped-old/0001/0556-Prevent-headless-pistons-from-being-created.patch diff --git a/patches/server-unmapped/0557-Add-BellRingEvent.patch b/patches/server-unmapped-old/0001/0557-Add-BellRingEvent.patch similarity index 100% rename from patches/server-unmapped/0557-Add-BellRingEvent.patch rename to patches/server-unmapped-old/0001/0557-Add-BellRingEvent.patch diff --git a/patches/server-unmapped/0558-Add-zombie-targets-turtle-egg-config.patch b/patches/server-unmapped-old/0001/0558-Add-zombie-targets-turtle-egg-config.patch similarity index 100% rename from patches/server-unmapped/0558-Add-zombie-targets-turtle-egg-config.patch rename to patches/server-unmapped-old/0001/0558-Add-zombie-targets-turtle-egg-config.patch diff --git a/patches/server-unmapped/0559-Buffer-joins-to-world.patch b/patches/server-unmapped-old/0001/0559-Buffer-joins-to-world.patch similarity index 100% rename from patches/server-unmapped/0559-Buffer-joins-to-world.patch rename to patches/server-unmapped-old/0001/0559-Buffer-joins-to-world.patch diff --git a/patches/server-unmapped/0560-Optimize-redstone-algorithm.patch b/patches/server-unmapped-old/0001/0560-Optimize-redstone-algorithm.patch similarity index 100% rename from patches/server-unmapped/0560-Optimize-redstone-algorithm.patch rename to patches/server-unmapped-old/0001/0560-Optimize-redstone-algorithm.patch diff --git a/patches/server-unmapped/0561-Fix-hex-colors-not-working-in-some-kick-messages.patch b/patches/server-unmapped-old/0001/0561-Fix-hex-colors-not-working-in-some-kick-messages.patch similarity index 100% rename from patches/server-unmapped/0561-Fix-hex-colors-not-working-in-some-kick-messages.patch rename to patches/server-unmapped-old/0001/0561-Fix-hex-colors-not-working-in-some-kick-messages.patch diff --git a/patches/server-unmapped/0562-PortalCreateEvent-needs-to-know-its-entity.patch b/patches/server-unmapped-old/0001/0562-PortalCreateEvent-needs-to-know-its-entity.patch similarity index 100% rename from patches/server-unmapped/0562-PortalCreateEvent-needs-to-know-its-entity.patch rename to patches/server-unmapped-old/0001/0562-PortalCreateEvent-needs-to-know-its-entity.patch diff --git a/patches/server-unmapped/0563-Fix-CraftTeam-null-check.patch b/patches/server-unmapped-old/0001/0563-Fix-CraftTeam-null-check.patch similarity index 100% rename from patches/server-unmapped/0563-Fix-CraftTeam-null-check.patch rename to patches/server-unmapped-old/0001/0563-Fix-CraftTeam-null-check.patch diff --git a/patches/server-unmapped/0564-Add-more-Evoker-API.patch b/patches/server-unmapped-old/0001/0564-Add-more-Evoker-API.patch similarity index 100% rename from patches/server-unmapped/0564-Add-more-Evoker-API.patch rename to patches/server-unmapped-old/0001/0564-Add-more-Evoker-API.patch diff --git a/patches/server-unmapped/0565-Add-a-way-to-get-translation-keys-for-blocks-entitie.patch b/patches/server-unmapped-old/0001/0565-Add-a-way-to-get-translation-keys-for-blocks-entitie.patch similarity index 100% rename from patches/server-unmapped/0565-Add-a-way-to-get-translation-keys-for-blocks-entitie.patch rename to patches/server-unmapped-old/0001/0565-Add-a-way-to-get-translation-keys-for-blocks-entitie.patch diff --git a/patches/server-unmapped/0566-Create-HoverEvent-from-ItemStack-Entity.patch b/patches/server-unmapped-old/0001/0566-Create-HoverEvent-from-ItemStack-Entity.patch similarity index 100% rename from patches/server-unmapped/0566-Create-HoverEvent-from-ItemStack-Entity.patch rename to patches/server-unmapped-old/0001/0566-Create-HoverEvent-from-ItemStack-Entity.patch diff --git a/patches/server-unmapped/0567-Cache-block-data-strings.patch b/patches/server-unmapped-old/0001/0567-Cache-block-data-strings.patch similarity index 100% rename from patches/server-unmapped/0567-Cache-block-data-strings.patch rename to patches/server-unmapped-old/0001/0567-Cache-block-data-strings.patch diff --git a/patches/server-unmapped/0568-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/patches/server-unmapped-old/0001/0568-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch similarity index 100% rename from patches/server-unmapped/0568-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch rename to patches/server-unmapped-old/0001/0568-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch diff --git a/patches/server-unmapped/0569-Add-additional-open-container-api-to-HumanEntity.patch b/patches/server-unmapped-old/0001/0569-Add-additional-open-container-api-to-HumanEntity.patch similarity index 100% rename from patches/server-unmapped/0569-Add-additional-open-container-api-to-HumanEntity.patch rename to patches/server-unmapped-old/0001/0569-Add-additional-open-container-api-to-HumanEntity.patch diff --git a/patches/server-unmapped/0570-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch b/patches/server-unmapped-old/0001/0570-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch similarity index 100% rename from patches/server-unmapped/0570-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch rename to patches/server-unmapped-old/0001/0570-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch diff --git a/patches/server-unmapped/0571-Extend-block-drop-capture-to-capture-all-items-added.patch b/patches/server-unmapped-old/0001/0571-Extend-block-drop-capture-to-capture-all-items-added.patch similarity index 100% rename from patches/server-unmapped/0571-Extend-block-drop-capture-to-capture-all-items-added.patch rename to patches/server-unmapped-old/0001/0571-Extend-block-drop-capture-to-capture-all-items-added.patch diff --git a/patches/server-unmapped/0572-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch b/patches/server-unmapped-old/0001/0572-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch similarity index 100% rename from patches/server-unmapped/0572-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch rename to patches/server-unmapped-old/0001/0572-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch diff --git a/patches/server-unmapped/0573-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch b/patches/server-unmapped-old/0001/0573-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch similarity index 100% rename from patches/server-unmapped/0573-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch rename to patches/server-unmapped-old/0001/0573-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch diff --git a/patches/server-unmapped/0574-Lazily-track-plugin-scoreboards-by-default.patch b/patches/server-unmapped-old/0001/0574-Lazily-track-plugin-scoreboards-by-default.patch similarity index 100% rename from patches/server-unmapped/0574-Lazily-track-plugin-scoreboards-by-default.patch rename to patches/server-unmapped-old/0001/0574-Lazily-track-plugin-scoreboards-by-default.patch diff --git a/patches/server-unmapped/0575-Entity-isTicking.patch b/patches/server-unmapped-old/0001/0575-Entity-isTicking.patch similarity index 100% rename from patches/server-unmapped/0575-Entity-isTicking.patch rename to patches/server-unmapped-old/0001/0575-Entity-isTicking.patch diff --git a/patches/server-unmapped/0576-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch b/patches/server-unmapped-old/0001/0576-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch similarity index 100% rename from patches/server-unmapped/0576-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch rename to patches/server-unmapped-old/0001/0576-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch diff --git a/patches/server-unmapped/0577-Fix-Not-a-string-Map-Conversion-spam.patch b/patches/server-unmapped-old/0001/0577-Fix-Not-a-string-Map-Conversion-spam.patch similarity index 100% rename from patches/server-unmapped/0577-Fix-Not-a-string-Map-Conversion-spam.patch rename to patches/server-unmapped-old/0001/0577-Fix-Not-a-string-Map-Conversion-spam.patch diff --git a/patches/server-unmapped/0578-Fix-CME-on-adding-a-passenger-in-CreatureSpawnEvent.patch b/patches/server-unmapped-old/0001/0578-Fix-CME-on-adding-a-passenger-in-CreatureSpawnEvent.patch similarity index 100% rename from patches/server-unmapped/0578-Fix-CME-on-adding-a-passenger-in-CreatureSpawnEvent.patch rename to patches/server-unmapped-old/0001/0578-Fix-CME-on-adding-a-passenger-in-CreatureSpawnEvent.patch diff --git a/patches/server-unmapped/0579-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch b/patches/server-unmapped-old/0001/0579-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch similarity index 100% rename from patches/server-unmapped/0579-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch rename to patches/server-unmapped-old/0001/0579-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch diff --git a/patches/server-unmapped/0580-Reset-Ender-Crystals-on-Dragon-Spawn.patch b/patches/server-unmapped-old/0001/0580-Reset-Ender-Crystals-on-Dragon-Spawn.patch similarity index 100% rename from patches/server-unmapped/0580-Reset-Ender-Crystals-on-Dragon-Spawn.patch rename to patches/server-unmapped-old/0001/0580-Reset-Ender-Crystals-on-Dragon-Spawn.patch diff --git a/patches/server-unmapped/0581-Fix-for-large-move-vectors-crashing-server.patch b/patches/server-unmapped-old/0001/0581-Fix-for-large-move-vectors-crashing-server.patch similarity index 100% rename from patches/server-unmapped/0581-Fix-for-large-move-vectors-crashing-server.patch rename to patches/server-unmapped-old/0001/0581-Fix-for-large-move-vectors-crashing-server.patch diff --git a/patches/server-unmapped/0582-Optimise-getType-calls.patch b/patches/server-unmapped-old/0001/0582-Optimise-getType-calls.patch similarity index 100% rename from patches/server-unmapped/0582-Optimise-getType-calls.patch rename to patches/server-unmapped-old/0001/0582-Optimise-getType-calls.patch diff --git a/patches/server-unmapped/0583-Villager-resetOffers.patch b/patches/server-unmapped-old/0001/0583-Villager-resetOffers.patch similarity index 100% rename from patches/server-unmapped/0583-Villager-resetOffers.patch rename to patches/server-unmapped-old/0001/0583-Villager-resetOffers.patch diff --git a/patches/server-unmapped/0584-Improve-inlinig-for-some-hot-IBlockData-methods.patch b/patches/server-unmapped-old/0001/0584-Improve-inlinig-for-some-hot-IBlockData-methods.patch similarity index 100% rename from patches/server-unmapped/0584-Improve-inlinig-for-some-hot-IBlockData-methods.patch rename to patches/server-unmapped-old/0001/0584-Improve-inlinig-for-some-hot-IBlockData-methods.patch diff --git a/patches/server-unmapped/0585-Retain-block-place-order-when-capturing-blockstates.patch b/patches/server-unmapped-old/0001/0585-Retain-block-place-order-when-capturing-blockstates.patch similarity index 100% rename from patches/server-unmapped/0585-Retain-block-place-order-when-capturing-blockstates.patch rename to patches/server-unmapped-old/0001/0585-Retain-block-place-order-when-capturing-blockstates.patch diff --git a/patches/server-unmapped/0586-Reduce-blockpos-allocation-from-pathfinding.patch b/patches/server-unmapped-old/0001/0586-Reduce-blockpos-allocation-from-pathfinding.patch similarity index 100% rename from patches/server-unmapped/0586-Reduce-blockpos-allocation-from-pathfinding.patch rename to patches/server-unmapped-old/0001/0586-Reduce-blockpos-allocation-from-pathfinding.patch diff --git a/patches/server-unmapped/0587-Fix-item-locations-dropped-from-campfires.patch b/patches/server-unmapped-old/0001/0587-Fix-item-locations-dropped-from-campfires.patch similarity index 100% rename from patches/server-unmapped/0587-Fix-item-locations-dropped-from-campfires.patch rename to patches/server-unmapped-old/0001/0587-Fix-item-locations-dropped-from-campfires.patch diff --git a/patches/server-unmapped/0588-Player-elytra-boost-API.patch b/patches/server-unmapped-old/0001/0588-Player-elytra-boost-API.patch similarity index 100% rename from patches/server-unmapped/0588-Player-elytra-boost-API.patch rename to patches/server-unmapped-old/0001/0588-Player-elytra-boost-API.patch diff --git a/patches/server-unmapped/0589-Fixed-TileEntityBell-memory-leak.patch b/patches/server-unmapped-old/0001/0589-Fixed-TileEntityBell-memory-leak.patch similarity index 100% rename from patches/server-unmapped/0589-Fixed-TileEntityBell-memory-leak.patch rename to patches/server-unmapped-old/0001/0589-Fixed-TileEntityBell-memory-leak.patch diff --git a/patches/server-unmapped/0590-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch b/patches/server-unmapped-old/0001/0590-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch similarity index 100% rename from patches/server-unmapped/0590-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch rename to patches/server-unmapped-old/0001/0590-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch diff --git a/patches/server-unmapped/0591-Add-getOfflinePlayerIfCached-String.patch b/patches/server-unmapped-old/0001/0591-Add-getOfflinePlayerIfCached-String.patch similarity index 100% rename from patches/server-unmapped/0591-Add-getOfflinePlayerIfCached-String.patch rename to patches/server-unmapped-old/0001/0591-Add-getOfflinePlayerIfCached-String.patch diff --git a/patches/server-unmapped/0592-Add-ignore-discounts-API.patch b/patches/server-unmapped-old/0001/0592-Add-ignore-discounts-API.patch similarity index 100% rename from patches/server-unmapped/0592-Add-ignore-discounts-API.patch rename to patches/server-unmapped-old/0001/0592-Add-ignore-discounts-API.patch diff --git a/patches/server-unmapped/0593-Toggle-for-removing-existing-dragon.patch b/patches/server-unmapped-old/0001/0593-Toggle-for-removing-existing-dragon.patch similarity index 100% rename from patches/server-unmapped/0593-Toggle-for-removing-existing-dragon.patch rename to patches/server-unmapped-old/0001/0593-Toggle-for-removing-existing-dragon.patch diff --git a/patches/server-unmapped/0594-Fix-client-lag-on-advancement-loading.patch b/patches/server-unmapped-old/0001/0594-Fix-client-lag-on-advancement-loading.patch similarity index 100% rename from patches/server-unmapped/0594-Fix-client-lag-on-advancement-loading.patch rename to patches/server-unmapped-old/0001/0594-Fix-client-lag-on-advancement-loading.patch diff --git a/patches/server-unmapped/0595-Item-no-age-no-player-pickup.patch b/patches/server-unmapped-old/0001/0595-Item-no-age-no-player-pickup.patch similarity index 100% rename from patches/server-unmapped/0595-Item-no-age-no-player-pickup.patch rename to patches/server-unmapped-old/0001/0595-Item-no-age-no-player-pickup.patch diff --git a/patches/server-unmapped/0596-Beacon-API-custom-effect-ranges.patch b/patches/server-unmapped-old/0001/0596-Beacon-API-custom-effect-ranges.patch similarity index 100% rename from patches/server-unmapped/0596-Beacon-API-custom-effect-ranges.patch rename to patches/server-unmapped-old/0001/0596-Beacon-API-custom-effect-ranges.patch diff --git a/patches/server-unmapped/0597-Add-API-for-quit-reason.patch b/patches/server-unmapped-old/0001/0597-Add-API-for-quit-reason.patch similarity index 100% rename from patches/server-unmapped/0597-Add-API-for-quit-reason.patch rename to patches/server-unmapped-old/0001/0597-Add-API-for-quit-reason.patch diff --git a/patches/server-unmapped/0598-Seed-based-feature-search.patch b/patches/server-unmapped-old/0001/0598-Seed-based-feature-search.patch similarity index 100% rename from patches/server-unmapped/0598-Seed-based-feature-search.patch rename to patches/server-unmapped-old/0001/0598-Seed-based-feature-search.patch diff --git a/patches/server-unmapped/0599-Add-Wandering-Trader-spawn-rate-config-options.patch b/patches/server-unmapped-old/0001/0599-Add-Wandering-Trader-spawn-rate-config-options.patch similarity index 100% rename from patches/server-unmapped/0599-Add-Wandering-Trader-spawn-rate-config-options.patch rename to patches/server-unmapped-old/0001/0599-Add-Wandering-Trader-spawn-rate-config-options.patch diff --git a/patches/server-unmapped/0600-Significantly-improve-performance-of-the-end-generat.patch b/patches/server-unmapped-old/0001/0600-Significantly-improve-performance-of-the-end-generat.patch similarity index 100% rename from patches/server-unmapped/0600-Significantly-improve-performance-of-the-end-generat.patch rename to patches/server-unmapped-old/0001/0600-Significantly-improve-performance-of-the-end-generat.patch diff --git a/patches/server-unmapped/0601-Expose-world-spawn-angle.patch b/patches/server-unmapped-old/0001/0601-Expose-world-spawn-angle.patch similarity index 100% rename from patches/server-unmapped/0601-Expose-world-spawn-angle.patch rename to patches/server-unmapped-old/0001/0601-Expose-world-spawn-angle.patch diff --git a/patches/server-unmapped/0602-Add-Destroy-Speed-API.patch b/patches/server-unmapped-old/0001/0602-Add-Destroy-Speed-API.patch similarity index 100% rename from patches/server-unmapped/0602-Add-Destroy-Speed-API.patch rename to patches/server-unmapped-old/0001/0602-Add-Destroy-Speed-API.patch diff --git a/patches/server-unmapped/0603-Fix-Player-spawnParticle-x-y-z-precision-loss.patch b/patches/server-unmapped-old/0001/0603-Fix-Player-spawnParticle-x-y-z-precision-loss.patch similarity index 100% rename from patches/server-unmapped/0603-Fix-Player-spawnParticle-x-y-z-precision-loss.patch rename to patches/server-unmapped-old/0001/0603-Fix-Player-spawnParticle-x-y-z-precision-loss.patch diff --git a/patches/server-unmapped/0604-Add-LivingEntity-clearActiveItem.patch b/patches/server-unmapped-old/0001/0604-Add-LivingEntity-clearActiveItem.patch similarity index 100% rename from patches/server-unmapped/0604-Add-LivingEntity-clearActiveItem.patch rename to patches/server-unmapped-old/0001/0604-Add-LivingEntity-clearActiveItem.patch diff --git a/patches/server-unmapped/0605-Add-PlayerItemCooldownEvent.patch b/patches/server-unmapped-old/0001/0605-Add-PlayerItemCooldownEvent.patch similarity index 100% rename from patches/server-unmapped/0605-Add-PlayerItemCooldownEvent.patch rename to patches/server-unmapped-old/0001/0605-Add-PlayerItemCooldownEvent.patch diff --git a/patches/server-unmapped/0606-More-lightning-API.patch b/patches/server-unmapped-old/0001/0606-More-lightning-API.patch similarity index 100% rename from patches/server-unmapped/0606-More-lightning-API.patch rename to patches/server-unmapped-old/0001/0606-More-lightning-API.patch diff --git a/patches/server-unmapped/0607-Climbing-should-not-bypass-cramming-gamerule.patch b/patches/server-unmapped-old/0001/0607-Climbing-should-not-bypass-cramming-gamerule.patch similarity index 100% rename from patches/server-unmapped/0607-Climbing-should-not-bypass-cramming-gamerule.patch rename to patches/server-unmapped-old/0001/0607-Climbing-should-not-bypass-cramming-gamerule.patch diff --git a/patches/server-unmapped/0608-Added-missing-default-perms-for-commands.patch b/patches/server-unmapped-old/0001/0608-Added-missing-default-perms-for-commands.patch similarity index 100% rename from patches/server-unmapped/0608-Added-missing-default-perms-for-commands.patch rename to patches/server-unmapped-old/0001/0608-Added-missing-default-perms-for-commands.patch diff --git a/patches/server-unmapped/0609-Add-PlayerShearBlockEvent.patch b/patches/server-unmapped-old/0001/0609-Add-PlayerShearBlockEvent.patch similarity index 100% rename from patches/server-unmapped/0609-Add-PlayerShearBlockEvent.patch rename to patches/server-unmapped-old/0001/0609-Add-PlayerShearBlockEvent.patch diff --git a/patches/server-unmapped/0610-Add-warning-for-servers-not-running-on-Java-11.patch b/patches/server-unmapped-old/0001/0610-Add-warning-for-servers-not-running-on-Java-11.patch similarity index 100% rename from patches/server-unmapped/0610-Add-warning-for-servers-not-running-on-Java-11.patch rename to patches/server-unmapped-old/0001/0610-Add-warning-for-servers-not-running-on-Java-11.patch diff --git a/patches/server-unmapped/0611-Set-spigots-verbose-world-setting-to-false-by-def.patch b/patches/server-unmapped-old/0001/0611-Set-spigots-verbose-world-setting-to-false-by-def.patch similarity index 100% rename from patches/server-unmapped/0611-Set-spigots-verbose-world-setting-to-false-by-def.patch rename to patches/server-unmapped-old/0001/0611-Set-spigots-verbose-world-setting-to-false-by-def.patch diff --git a/patches/server-unmapped/0612-Fix-curing-zombie-villager-discount-exploit.patch b/patches/server-unmapped-old/0001/0612-Fix-curing-zombie-villager-discount-exploit.patch similarity index 100% rename from patches/server-unmapped/0612-Fix-curing-zombie-villager-discount-exploit.patch rename to patches/server-unmapped-old/0001/0612-Fix-curing-zombie-villager-discount-exploit.patch diff --git a/patches/server-unmapped/0613-Limit-recipe-packets.patch b/patches/server-unmapped-old/0001/0613-Limit-recipe-packets.patch similarity index 100% rename from patches/server-unmapped/0613-Limit-recipe-packets.patch rename to patches/server-unmapped-old/0001/0613-Limit-recipe-packets.patch diff --git a/patches/server-unmapped/0614-Fix-CraftSound-backwards-compatibility.patch b/patches/server-unmapped-old/0001/0614-Fix-CraftSound-backwards-compatibility.patch similarity index 100% rename from patches/server-unmapped/0614-Fix-CraftSound-backwards-compatibility.patch rename to patches/server-unmapped-old/0001/0614-Fix-CraftSound-backwards-compatibility.patch diff --git a/patches/server-unmapped/0615-MC-4-Fix-item-position-desync.patch b/patches/server-unmapped-old/0001/0615-MC-4-Fix-item-position-desync.patch similarity index 100% rename from patches/server-unmapped/0615-MC-4-Fix-item-position-desync.patch rename to patches/server-unmapped-old/0001/0615-MC-4-Fix-item-position-desync.patch diff --git a/patches/server-unmapped/0616-Player-Chunk-Load-Unload-Events.patch b/patches/server-unmapped-old/0001/0616-Player-Chunk-Load-Unload-Events.patch similarity index 100% rename from patches/server-unmapped/0616-Player-Chunk-Load-Unload-Events.patch rename to patches/server-unmapped-old/0001/0616-Player-Chunk-Load-Unload-Events.patch diff --git a/patches/server-unmapped/0617-Optimize-Dynamic-get-Missing-Keys.patch b/patches/server-unmapped-old/0001/0617-Optimize-Dynamic-get-Missing-Keys.patch similarity index 100% rename from patches/server-unmapped/0617-Optimize-Dynamic-get-Missing-Keys.patch rename to patches/server-unmapped-old/0001/0617-Optimize-Dynamic-get-Missing-Keys.patch diff --git a/patches/server-unmapped/0618-Expose-LivingEntity-hurt-direction.patch b/patches/server-unmapped-old/0001/0618-Expose-LivingEntity-hurt-direction.patch similarity index 100% rename from patches/server-unmapped/0618-Expose-LivingEntity-hurt-direction.patch rename to patches/server-unmapped-old/0001/0618-Expose-LivingEntity-hurt-direction.patch diff --git a/patches/server-unmapped/0619-Add-OBSTRUCTED-reason-to-BedEnterResult.patch b/patches/server-unmapped-old/0001/0619-Add-OBSTRUCTED-reason-to-BedEnterResult.patch similarity index 100% rename from patches/server-unmapped/0619-Add-OBSTRUCTED-reason-to-BedEnterResult.patch rename to patches/server-unmapped-old/0001/0619-Add-OBSTRUCTED-reason-to-BedEnterResult.patch diff --git a/patches/server-unmapped/0620-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch b/patches/server-unmapped-old/0001/0620-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch similarity index 100% rename from patches/server-unmapped/0620-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch rename to patches/server-unmapped-old/0001/0620-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch diff --git a/patches/server-unmapped/0621-added-PlayerTradeEvent.patch b/patches/server-unmapped-old/0001/0621-added-PlayerTradeEvent.patch similarity index 100% rename from patches/server-unmapped/0621-added-PlayerTradeEvent.patch rename to patches/server-unmapped-old/0001/0621-added-PlayerTradeEvent.patch diff --git a/patches/server-unmapped/0622-Implement-TargetHitEvent.patch b/patches/server-unmapped-old/0001/0622-Implement-TargetHitEvent.patch similarity index 100% rename from patches/server-unmapped/0622-Implement-TargetHitEvent.patch rename to patches/server-unmapped-old/0001/0622-Implement-TargetHitEvent.patch diff --git a/patches/server-unmapped/0623-Additional-Block-Material-API-s.patch b/patches/server-unmapped-old/0001/0623-Additional-Block-Material-API-s.patch similarity index 100% rename from patches/server-unmapped/0623-Additional-Block-Material-API-s.patch rename to patches/server-unmapped-old/0001/0623-Additional-Block-Material-API-s.patch diff --git a/patches/server-unmapped/0624-Fix-harming-potion-dupe.patch b/patches/server-unmapped-old/0001/0624-Fix-harming-potion-dupe.patch similarity index 100% rename from patches/server-unmapped/0624-Fix-harming-potion-dupe.patch rename to patches/server-unmapped-old/0001/0624-Fix-harming-potion-dupe.patch diff --git a/patches/server-unmapped/0625-Implement-API-to-get-Material-from-Boats-and-Minecar.patch b/patches/server-unmapped-old/0001/0625-Implement-API-to-get-Material-from-Boats-and-Minecar.patch similarity index 100% rename from patches/server-unmapped/0625-Implement-API-to-get-Material-from-Boats-and-Minecar.patch rename to patches/server-unmapped-old/0001/0625-Implement-API-to-get-Material-from-Boats-and-Minecar.patch diff --git a/patches/server-unmapped/0626-Optimized-tick-ready-check.patch b/patches/server-unmapped-old/0001/0626-Optimized-tick-ready-check.patch similarity index 100% rename from patches/server-unmapped/0626-Optimized-tick-ready-check.patch rename to patches/server-unmapped-old/0001/0626-Optimized-tick-ready-check.patch diff --git a/patches/server-unmapped/0627-Cache-burn-durations.patch b/patches/server-unmapped-old/0001/0627-Cache-burn-durations.patch similarity index 100% rename from patches/server-unmapped/0627-Cache-burn-durations.patch rename to patches/server-unmapped-old/0001/0627-Cache-burn-durations.patch diff --git a/patches/server-unmapped/0628-Allow-disabling-mob-spawner-spawn-egg-transformation.patch b/patches/server-unmapped-old/0001/0628-Allow-disabling-mob-spawner-spawn-egg-transformation.patch similarity index 100% rename from patches/server-unmapped/0628-Allow-disabling-mob-spawner-spawn-egg-transformation.patch rename to patches/server-unmapped-old/0001/0628-Allow-disabling-mob-spawner-spawn-egg-transformation.patch diff --git a/patches/server-unmapped/0629-Implement-PlayerFlowerPotManipulateEvent.patch b/patches/server-unmapped-old/0001/0629-Implement-PlayerFlowerPotManipulateEvent.patch similarity index 100% rename from patches/server-unmapped/0629-Implement-PlayerFlowerPotManipulateEvent.patch rename to patches/server-unmapped-old/0001/0629-Implement-PlayerFlowerPotManipulateEvent.patch diff --git a/patches/server-unmapped/0630-Fix-interact-event-not-being-called-in-adventure.patch b/patches/server-unmapped-old/0001/0630-Fix-interact-event-not-being-called-in-adventure.patch similarity index 100% rename from patches/server-unmapped/0630-Fix-interact-event-not-being-called-in-adventure.patch rename to patches/server-unmapped-old/0001/0630-Fix-interact-event-not-being-called-in-adventure.patch diff --git a/patches/server-unmapped/0631-Zombie-API-breaking-doors.patch b/patches/server-unmapped-old/0001/0631-Zombie-API-breaking-doors.patch similarity index 100% rename from patches/server-unmapped/0631-Zombie-API-breaking-doors.patch rename to patches/server-unmapped-old/0001/0631-Zombie-API-breaking-doors.patch diff --git a/patches/server-unmapped/0632-Fix-nerfed-slime-when-splitting.patch b/patches/server-unmapped-old/0001/0632-Fix-nerfed-slime-when-splitting.patch similarity index 100% rename from patches/server-unmapped/0632-Fix-nerfed-slime-when-splitting.patch rename to patches/server-unmapped-old/0001/0632-Fix-nerfed-slime-when-splitting.patch diff --git a/patches/server-unmapped/0633-Add-EntityLoadCrossbowEvent.patch b/patches/server-unmapped-old/0001/0633-Add-EntityLoadCrossbowEvent.patch similarity index 100% rename from patches/server-unmapped/0633-Add-EntityLoadCrossbowEvent.patch rename to patches/server-unmapped-old/0001/0633-Add-EntityLoadCrossbowEvent.patch diff --git a/patches/server-unmapped/0634-Guardian-beam-workaround.patch b/patches/server-unmapped-old/0001/0634-Guardian-beam-workaround.patch similarity index 100% rename from patches/server-unmapped/0634-Guardian-beam-workaround.patch rename to patches/server-unmapped-old/0001/0634-Guardian-beam-workaround.patch diff --git a/patches/server-unmapped/0635-Added-WorldGameRuleChangeEvent.patch b/patches/server-unmapped-old/0001/0635-Added-WorldGameRuleChangeEvent.patch similarity index 100% rename from patches/server-unmapped/0635-Added-WorldGameRuleChangeEvent.patch rename to patches/server-unmapped-old/0001/0635-Added-WorldGameRuleChangeEvent.patch diff --git a/patches/server-unmapped/0636-Added-ServerResourcesReloadedEvent.patch b/patches/server-unmapped-old/0001/0636-Added-ServerResourcesReloadedEvent.patch similarity index 100% rename from patches/server-unmapped/0636-Added-ServerResourcesReloadedEvent.patch rename to patches/server-unmapped-old/0001/0636-Added-ServerResourcesReloadedEvent.patch diff --git a/patches/server-unmapped/0637-Added-world-settings-for-mobs-picking-up-loot.patch b/patches/server-unmapped-old/0001/0637-Added-world-settings-for-mobs-picking-up-loot.patch similarity index 100% rename from patches/server-unmapped/0637-Added-world-settings-for-mobs-picking-up-loot.patch rename to patches/server-unmapped-old/0001/0637-Added-world-settings-for-mobs-picking-up-loot.patch diff --git a/patches/server-unmapped/0638-Implemented-BlockFailedDispenseEvent.patch b/patches/server-unmapped-old/0001/0638-Implemented-BlockFailedDispenseEvent.patch similarity index 100% rename from patches/server-unmapped/0638-Implemented-BlockFailedDispenseEvent.patch rename to patches/server-unmapped-old/0001/0638-Implemented-BlockFailedDispenseEvent.patch diff --git a/patches/server-unmapped/0639-Added-PlayerLecternPageChangeEvent.patch b/patches/server-unmapped-old/0001/0639-Added-PlayerLecternPageChangeEvent.patch similarity index 100% rename from patches/server-unmapped/0639-Added-PlayerLecternPageChangeEvent.patch rename to patches/server-unmapped-old/0001/0639-Added-PlayerLecternPageChangeEvent.patch diff --git a/patches/server-unmapped/0640-Fire-event-on-GS4-query.patch b/patches/server-unmapped-old/0001/0640-Fire-event-on-GS4-query.patch similarity index 100% rename from patches/server-unmapped/0640-Fire-event-on-GS4-query.patch rename to patches/server-unmapped-old/0001/0640-Fire-event-on-GS4-query.patch diff --git a/patches/server-unmapped/0641-Added-PlayerLoomPatternSelectEvent.patch b/patches/server-unmapped-old/0001/0641-Added-PlayerLoomPatternSelectEvent.patch similarity index 100% rename from patches/server-unmapped/0641-Added-PlayerLoomPatternSelectEvent.patch rename to patches/server-unmapped-old/0001/0641-Added-PlayerLoomPatternSelectEvent.patch diff --git a/patches/server-unmapped/0642-Configurable-door-breaking-difficulty.patch b/patches/server-unmapped-old/0001/0642-Configurable-door-breaking-difficulty.patch similarity index 100% rename from patches/server-unmapped/0642-Configurable-door-breaking-difficulty.patch rename to patches/server-unmapped-old/0001/0642-Configurable-door-breaking-difficulty.patch diff --git a/patches/server-unmapped/0643-Empty-commands-shall-not-be-dispatched.patch b/patches/server-unmapped-old/0001/0643-Empty-commands-shall-not-be-dispatched.patch similarity index 100% rename from patches/server-unmapped/0643-Empty-commands-shall-not-be-dispatched.patch rename to patches/server-unmapped-old/0001/0643-Empty-commands-shall-not-be-dispatched.patch diff --git a/patches/server-unmapped/0644-Implement-API-to-expose-exact-interaction-point.patch b/patches/server-unmapped-old/0001/0644-Implement-API-to-expose-exact-interaction-point.patch similarity index 100% rename from patches/server-unmapped/0644-Implement-API-to-expose-exact-interaction-point.patch rename to patches/server-unmapped-old/0001/0644-Implement-API-to-expose-exact-interaction-point.patch diff --git a/patches/server-unmapped/0645-Remove-stale-POIs.patch b/patches/server-unmapped-old/0001/0645-Remove-stale-POIs.patch similarity index 100% rename from patches/server-unmapped/0645-Remove-stale-POIs.patch rename to patches/server-unmapped-old/0001/0645-Remove-stale-POIs.patch diff --git a/patches/server-unmapped/0646-Fix-villager-boat-exploit.patch b/patches/server-unmapped-old/0001/0646-Fix-villager-boat-exploit.patch similarity index 100% rename from patches/server-unmapped/0646-Fix-villager-boat-exploit.patch rename to patches/server-unmapped-old/0001/0646-Fix-villager-boat-exploit.patch diff --git a/patches/server-unmapped/0647-Entity-load-save-limit-per-chunk.patch b/patches/server-unmapped-old/0001/0647-Entity-load-save-limit-per-chunk.patch similarity index 100% rename from patches/server-unmapped/0647-Entity-load-save-limit-per-chunk.patch rename to patches/server-unmapped-old/0001/0647-Entity-load-save-limit-per-chunk.patch diff --git a/patches/server-unmapped/0648-Add-sendOpLevel-API.patch b/patches/server-unmapped-old/0001/0648-Add-sendOpLevel-API.patch similarity index 100% rename from patches/server-unmapped/0648-Add-sendOpLevel-API.patch rename to patches/server-unmapped-old/0001/0648-Add-sendOpLevel-API.patch diff --git a/patches/server-unmapped/0649-Add-StructureLocateEvent.patch b/patches/server-unmapped-old/0001/0649-Add-StructureLocateEvent.patch similarity index 100% rename from patches/server-unmapped/0649-Add-StructureLocateEvent.patch rename to patches/server-unmapped-old/0001/0649-Add-StructureLocateEvent.patch diff --git a/patches/server-unmapped/0650-Collision-option-for-requiring-a-player-participant.patch b/patches/server-unmapped-old/0001/0650-Collision-option-for-requiring-a-player-participant.patch similarity index 100% rename from patches/server-unmapped/0650-Collision-option-for-requiring-a-player-participant.patch rename to patches/server-unmapped-old/0001/0650-Collision-option-for-requiring-a-player-participant.patch diff --git a/patches/server-unmapped/0651-Make-ProjectileHitEvent-Cancellable.patch b/patches/server-unmapped-old/0001/0651-Make-ProjectileHitEvent-Cancellable.patch similarity index 100% rename from patches/server-unmapped/0651-Make-ProjectileHitEvent-Cancellable.patch rename to patches/server-unmapped-old/0001/0651-Make-ProjectileHitEvent-Cancellable.patch diff --git a/patches/server-unmapped/0652-Return-chat-component-with-empty-text-instead-of-thr.patch b/patches/server-unmapped-old/0001/0652-Return-chat-component-with-empty-text-instead-of-thr.patch similarity index 100% rename from patches/server-unmapped/0652-Return-chat-component-with-empty-text-instead-of-thr.patch rename to patches/server-unmapped-old/0001/0652-Return-chat-component-with-empty-text-instead-of-thr.patch diff --git a/patches/server-unmapped/0653-Make-schedule-command-per-world.patch b/patches/server-unmapped-old/0001/0653-Make-schedule-command-per-world.patch similarity index 100% rename from patches/server-unmapped/0653-Make-schedule-command-per-world.patch rename to patches/server-unmapped-old/0001/0653-Make-schedule-command-per-world.patch diff --git a/patches/server-unmapped/0654-Configurable-max-leash-distance.patch b/patches/server-unmapped-old/0001/0654-Configurable-max-leash-distance.patch similarity index 100% rename from patches/server-unmapped/0654-Configurable-max-leash-distance.patch rename to patches/server-unmapped-old/0001/0654-Configurable-max-leash-distance.patch diff --git a/patches/server-unmapped/0655-Implement-BlockPreDispenseEvent.patch b/patches/server-unmapped-old/0001/0655-Implement-BlockPreDispenseEvent.patch similarity index 100% rename from patches/server-unmapped/0655-Implement-BlockPreDispenseEvent.patch rename to patches/server-unmapped-old/0001/0655-Implement-BlockPreDispenseEvent.patch diff --git a/patches/server-unmapped/0656-Added-Vanilla-Entity-Tags.patch b/patches/server-unmapped-old/0001/0656-Added-Vanilla-Entity-Tags.patch similarity index 100% rename from patches/server-unmapped/0656-Added-Vanilla-Entity-Tags.patch rename to patches/server-unmapped-old/0001/0656-Added-Vanilla-Entity-Tags.patch diff --git a/patches/server-unmapped/0657-added-Wither-API.patch b/patches/server-unmapped-old/0001/0657-added-Wither-API.patch similarity index 100% rename from patches/server-unmapped/0657-added-Wither-API.patch rename to patches/server-unmapped-old/0001/0657-added-Wither-API.patch diff --git a/patches/server-unmapped/0658-Added-firing-of-PlayerChangeBeaconEffectEvent.patch b/patches/server-unmapped-old/0001/0658-Added-firing-of-PlayerChangeBeaconEffectEvent.patch similarity index 100% rename from patches/server-unmapped/0658-Added-firing-of-PlayerChangeBeaconEffectEvent.patch rename to patches/server-unmapped-old/0001/0658-Added-firing-of-PlayerChangeBeaconEffectEvent.patch diff --git a/patches/server-unmapped/0659-Fix-console-spam-when-removing-chests-in-water.patch b/patches/server-unmapped-old/0001/0659-Fix-console-spam-when-removing-chests-in-water.patch similarity index 100% rename from patches/server-unmapped/0659-Fix-console-spam-when-removing-chests-in-water.patch rename to patches/server-unmapped-old/0001/0659-Fix-console-spam-when-removing-chests-in-water.patch diff --git a/patches/server-unmapped/0660-Add-toggle-for-always-placing-the-dragon-egg.patch b/patches/server-unmapped-old/0001/0660-Add-toggle-for-always-placing-the-dragon-egg.patch similarity index 100% rename from patches/server-unmapped/0660-Add-toggle-for-always-placing-the-dragon-egg.patch rename to patches/server-unmapped-old/0001/0660-Add-toggle-for-always-placing-the-dragon-egg.patch diff --git a/patches/server-unmapped/0661-Added-PlayerStonecutterRecipeSelectEvent.patch b/patches/server-unmapped-old/0001/0661-Added-PlayerStonecutterRecipeSelectEvent.patch similarity index 100% rename from patches/server-unmapped/0661-Added-PlayerStonecutterRecipeSelectEvent.patch rename to patches/server-unmapped-old/0001/0661-Added-PlayerStonecutterRecipeSelectEvent.patch diff --git a/patches/server-unmapped/0662-Add-dropLeash-variable-to-EntityUnleashEvent.patch b/patches/server-unmapped-old/0001/0662-Add-dropLeash-variable-to-EntityUnleashEvent.patch similarity index 100% rename from patches/server-unmapped/0662-Add-dropLeash-variable-to-EntityUnleashEvent.patch rename to patches/server-unmapped-old/0001/0662-Add-dropLeash-variable-to-EntityUnleashEvent.patch diff --git a/patches/server-unmapped/0663-Skip-distance-map-update-when-spawning-disabled.patch b/patches/server-unmapped-old/0001/0663-Skip-distance-map-update-when-spawning-disabled.patch similarity index 100% rename from patches/server-unmapped/0663-Skip-distance-map-update-when-spawning-disabled.patch rename to patches/server-unmapped-old/0001/0663-Skip-distance-map-update-when-spawning-disabled.patch diff --git a/patches/server-unmapped/0664-Reset-shield-blocking-on-dimension-change.patch b/patches/server-unmapped-old/0001/0664-Reset-shield-blocking-on-dimension-change.patch similarity index 100% rename from patches/server-unmapped/0664-Reset-shield-blocking-on-dimension-change.patch rename to patches/server-unmapped-old/0001/0664-Reset-shield-blocking-on-dimension-change.patch diff --git a/patches/server-unmapped/0665-add-DragonEggFormEvent.patch b/patches/server-unmapped-old/0001/0665-add-DragonEggFormEvent.patch similarity index 100% rename from patches/server-unmapped/0665-add-DragonEggFormEvent.patch rename to patches/server-unmapped-old/0001/0665-add-DragonEggFormEvent.patch diff --git a/patches/server-unmapped/0666-EntityMoveEvent.patch b/patches/server-unmapped-old/0001/0666-EntityMoveEvent.patch similarity index 100% rename from patches/server-unmapped/0666-EntityMoveEvent.patch rename to patches/server-unmapped-old/0001/0666-EntityMoveEvent.patch diff --git a/patches/server-unmapped/0667-added-option-to-disable-pathfinding-updates-on-block.patch b/patches/server-unmapped-old/0001/0667-added-option-to-disable-pathfinding-updates-on-block.patch similarity index 100% rename from patches/server-unmapped/0667-added-option-to-disable-pathfinding-updates-on-block.patch rename to patches/server-unmapped-old/0001/0667-added-option-to-disable-pathfinding-updates-on-block.patch diff --git a/patches/server-unmapped/0668-Inline-shift-direction-fields.patch b/patches/server-unmapped-old/0001/0668-Inline-shift-direction-fields.patch similarity index 100% rename from patches/server-unmapped/0668-Inline-shift-direction-fields.patch rename to patches/server-unmapped-old/0001/0668-Inline-shift-direction-fields.patch diff --git a/patches/server-unmapped/0669-Allow-adding-items-to-BlockDropItemEvent.patch b/patches/server-unmapped-old/0001/0669-Allow-adding-items-to-BlockDropItemEvent.patch similarity index 100% rename from patches/server-unmapped/0669-Allow-adding-items-to-BlockDropItemEvent.patch rename to patches/server-unmapped-old/0001/0669-Allow-adding-items-to-BlockDropItemEvent.patch diff --git a/patches/server-unmapped/0670-Add-getMainThreadExecutor-to-BukkitScheduler.patch b/patches/server-unmapped-old/0001/0670-Add-getMainThreadExecutor-to-BukkitScheduler.patch similarity index 100% rename from patches/server-unmapped/0670-Add-getMainThreadExecutor-to-BukkitScheduler.patch rename to patches/server-unmapped-old/0001/0670-Add-getMainThreadExecutor-to-BukkitScheduler.patch diff --git a/patches/server-unmapped/0671-living-entity-allow-attribute-registration.patch b/patches/server-unmapped-old/0001/0671-living-entity-allow-attribute-registration.patch similarity index 100% rename from patches/server-unmapped/0671-living-entity-allow-attribute-registration.patch rename to patches/server-unmapped-old/0001/0671-living-entity-allow-attribute-registration.patch diff --git a/patches/server-unmapped/0672-fix-dead-slime-setSize-invincibility.patch b/patches/server-unmapped-old/0001/0672-fix-dead-slime-setSize-invincibility.patch similarity index 100% rename from patches/server-unmapped/0672-fix-dead-slime-setSize-invincibility.patch rename to patches/server-unmapped-old/0001/0672-fix-dead-slime-setSize-invincibility.patch diff --git a/patches/server-unmapped/0673-Merchant-getRecipes-should-return-an-immutable-list.patch b/patches/server-unmapped-old/0001/0673-Merchant-getRecipes-should-return-an-immutable-list.patch similarity index 100% rename from patches/server-unmapped/0673-Merchant-getRecipes-should-return-an-immutable-list.patch rename to patches/server-unmapped-old/0001/0673-Merchant-getRecipes-should-return-an-immutable-list.patch diff --git a/patches/server-unmapped/0674-misc-debugging-dumps.patch b/patches/server-unmapped-old/0001/0674-misc-debugging-dumps.patch similarity index 100% rename from patches/server-unmapped/0674-misc-debugging-dumps.patch rename to patches/server-unmapped-old/0001/0674-misc-debugging-dumps.patch diff --git a/patches/server-unmapped/0675-Add-support-for-hex-color-codes-in-console.patch b/patches/server-unmapped-old/0001/0675-Add-support-for-hex-color-codes-in-console.patch similarity index 100% rename from patches/server-unmapped/0675-Add-support-for-hex-color-codes-in-console.patch rename to patches/server-unmapped-old/0001/0675-Add-support-for-hex-color-codes-in-console.patch diff --git a/patches/server-unmapped/0676-Clear-SyncLoadInfo.patch b/patches/server-unmapped-old/0001/0676-Clear-SyncLoadInfo.patch similarity index 100% rename from patches/server-unmapped/0676-Clear-SyncLoadInfo.patch rename to patches/server-unmapped-old/0001/0676-Clear-SyncLoadInfo.patch diff --git a/patches/server-unmapped/0677-Expose-Tracked-Players.patch b/patches/server-unmapped-old/0001/0677-Expose-Tracked-Players.patch similarity index 100% rename from patches/server-unmapped/0677-Expose-Tracked-Players.patch rename to patches/server-unmapped-old/0001/0677-Expose-Tracked-Players.patch diff --git a/patches/server-unmapped/0678-Remove-streams-from-SensorNearest.patch b/patches/server-unmapped-old/0001/0678-Remove-streams-from-SensorNearest.patch similarity index 100% rename from patches/server-unmapped/0678-Remove-streams-from-SensorNearest.patch rename to patches/server-unmapped-old/0001/0678-Remove-streams-from-SensorNearest.patch diff --git a/patches/server-unmapped/0679-do-not-create-unnecessary-copies-of-passenger-list.patch b/patches/server-unmapped-old/0001/0679-do-not-create-unnecessary-copies-of-passenger-list.patch similarity index 100% rename from patches/server-unmapped/0679-do-not-create-unnecessary-copies-of-passenger-list.patch rename to patches/server-unmapped-old/0001/0679-do-not-create-unnecessary-copies-of-passenger-list.patch diff --git a/patches/server-unmapped/0680-MC-29274-Fix-Wither-hostility-towards-players.patch b/patches/server-unmapped-old/0001/0680-MC-29274-Fix-Wither-hostility-towards-players.patch similarity index 100% rename from patches/server-unmapped/0680-MC-29274-Fix-Wither-hostility-towards-players.patch rename to patches/server-unmapped-old/0001/0680-MC-29274-Fix-Wither-hostility-towards-players.patch diff --git a/patches/server-unmapped/0681-Throw-proper-exception-on-empty-JsonList-file.patch b/patches/server-unmapped-old/0001/0681-Throw-proper-exception-on-empty-JsonList-file.patch similarity index 100% rename from patches/server-unmapped/0681-Throw-proper-exception-on-empty-JsonList-file.patch rename to patches/server-unmapped-old/0001/0681-Throw-proper-exception-on-empty-JsonList-file.patch diff --git a/patches/server-unmapped/0682-Improve-ServerGUI.patch b/patches/server-unmapped-old/0001/0682-Improve-ServerGUI.patch similarity index 100% rename from patches/server-unmapped/0682-Improve-ServerGUI.patch rename to patches/server-unmapped-old/0001/0682-Improve-ServerGUI.patch diff --git a/patches/server-unmapped/0683-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch b/patches/server-unmapped-old/0001/0683-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch similarity index 100% rename from patches/server-unmapped/0683-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch rename to patches/server-unmapped-old/0001/0683-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch diff --git a/patches/server-unmapped/0684-fix-converting-txt-to-json-file.patch b/patches/server-unmapped-old/0001/0684-fix-converting-txt-to-json-file.patch similarity index 100% rename from patches/server-unmapped/0684-fix-converting-txt-to-json-file.patch rename to patches/server-unmapped-old/0001/0684-fix-converting-txt-to-json-file.patch diff --git a/patches/server-unmapped/0685-Add-worldborder-events.patch b/patches/server-unmapped-old/0001/0685-Add-worldborder-events.patch similarity index 100% rename from patches/server-unmapped/0685-Add-worldborder-events.patch rename to patches/server-unmapped-old/0001/0685-Add-worldborder-events.patch diff --git a/patches/server-unmapped/0686-added-PlayerNameEntityEvent.patch b/patches/server-unmapped-old/0001/0686-added-PlayerNameEntityEvent.patch similarity index 100% rename from patches/server-unmapped/0686-added-PlayerNameEntityEvent.patch rename to patches/server-unmapped-old/0001/0686-added-PlayerNameEntityEvent.patch diff --git a/patches/server-unmapped/0687-Prevent-grindstones-from-overstacking-items.patch b/patches/server-unmapped-old/0001/0687-Prevent-grindstones-from-overstacking-items.patch similarity index 100% rename from patches/server-unmapped/0687-Prevent-grindstones-from-overstacking-items.patch rename to patches/server-unmapped-old/0001/0687-Prevent-grindstones-from-overstacking-items.patch diff --git a/patches/server-unmapped/0688-Add-recipe-to-cook-events.patch b/patches/server-unmapped-old/0001/0688-Add-recipe-to-cook-events.patch similarity index 100% rename from patches/server-unmapped/0688-Add-recipe-to-cook-events.patch rename to patches/server-unmapped-old/0001/0688-Add-recipe-to-cook-events.patch diff --git a/patches/server-unmapped/0689-Add-Block-isValidTool.patch b/patches/server-unmapped-old/0001/0689-Add-Block-isValidTool.patch similarity index 100% rename from patches/server-unmapped/0689-Add-Block-isValidTool.patch rename to patches/server-unmapped-old/0001/0689-Add-Block-isValidTool.patch diff --git a/patches/server-unmapped/0690-Allow-using-signs-inside-spawn-protection.patch b/patches/server-unmapped-old/0001/0690-Allow-using-signs-inside-spawn-protection.patch similarity index 100% rename from patches/server-unmapped/0690-Allow-using-signs-inside-spawn-protection.patch rename to patches/server-unmapped-old/0001/0690-Allow-using-signs-inside-spawn-protection.patch diff --git a/patches/server-unmapped/0691-Implement-Keyed-on-World.patch b/patches/server-unmapped-old/0001/0691-Implement-Keyed-on-World.patch similarity index 100% rename from patches/server-unmapped/0691-Implement-Keyed-on-World.patch rename to patches/server-unmapped-old/0001/0691-Implement-Keyed-on-World.patch diff --git a/patches/server-unmapped/0692-Add-fast-alternative-constructor-for-Vector3f.patch b/patches/server-unmapped-old/0001/0692-Add-fast-alternative-constructor-for-Vector3f.patch similarity index 100% rename from patches/server-unmapped/0692-Add-fast-alternative-constructor-for-Vector3f.patch rename to patches/server-unmapped-old/0001/0692-Add-fast-alternative-constructor-for-Vector3f.patch diff --git a/patches/server-unmapped/0693-Item-Rarity-API.patch b/patches/server-unmapped-old/0001/0693-Item-Rarity-API.patch similarity index 100% rename from patches/server-unmapped/0693-Item-Rarity-API.patch rename to patches/server-unmapped-old/0001/0693-Item-Rarity-API.patch diff --git a/patches/server-unmapped/0694-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch b/patches/server-unmapped-old/0001/0694-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch similarity index 100% rename from patches/server-unmapped/0694-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch rename to patches/server-unmapped-old/0001/0694-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch diff --git a/patches/server-unmapped/0695-copy-TESign-isEditable-from-snapshots.patch b/patches/server-unmapped-old/0001/0695-copy-TESign-isEditable-from-snapshots.patch similarity index 100% rename from patches/server-unmapped/0695-copy-TESign-isEditable-from-snapshots.patch rename to patches/server-unmapped-old/0001/0695-copy-TESign-isEditable-from-snapshots.patch diff --git a/patches/server-unmapped/0696-Drop-carried-item-when-player-has-disconnected.patch b/patches/server-unmapped-old/0001/0696-Drop-carried-item-when-player-has-disconnected.patch similarity index 100% rename from patches/server-unmapped/0696-Drop-carried-item-when-player-has-disconnected.patch rename to patches/server-unmapped-old/0001/0696-Drop-carried-item-when-player-has-disconnected.patch diff --git a/patches/server-unmapped/0697-forced-whitelist-use-configurable-kick-message.patch b/patches/server-unmapped-old/0001/0697-forced-whitelist-use-configurable-kick-message.patch similarity index 100% rename from patches/server-unmapped/0697-forced-whitelist-use-configurable-kick-message.patch rename to patches/server-unmapped-old/0001/0697-forced-whitelist-use-configurable-kick-message.patch diff --git a/patches/server-unmapped/0698-Make-sure-to-remove-correct-TE-during-TE-tick.patch b/patches/server-unmapped-old/0001/0698-Make-sure-to-remove-correct-TE-during-TE-tick.patch similarity index 100% rename from patches/server-unmapped/0698-Make-sure-to-remove-correct-TE-during-TE-tick.patch rename to patches/server-unmapped-old/0001/0698-Make-sure-to-remove-correct-TE-during-TE-tick.patch diff --git a/patches/server-unmapped/0699-Don-t-ignore-result-of-PlayerEditBookEvent.patch b/patches/server-unmapped-old/0001/0699-Don-t-ignore-result-of-PlayerEditBookEvent.patch similarity index 100% rename from patches/server-unmapped/0699-Don-t-ignore-result-of-PlayerEditBookEvent.patch rename to patches/server-unmapped-old/0001/0699-Don-t-ignore-result-of-PlayerEditBookEvent.patch diff --git a/patches/server-unmapped/0700-Expose-protocol-version.patch b/patches/server-unmapped-old/0001/0700-Expose-protocol-version.patch similarity index 100% rename from patches/server-unmapped/0700-Expose-protocol-version.patch rename to patches/server-unmapped-old/0001/0700-Expose-protocol-version.patch diff --git a/patches/server-unmapped/0701-Enhance-console-tab-completions-for-brigadier-comman.patch b/patches/server-unmapped-old/0001/0701-Enhance-console-tab-completions-for-brigadier-comman.patch similarity index 100% rename from patches/server-unmapped/0701-Enhance-console-tab-completions-for-brigadier-comman.patch rename to patches/server-unmapped-old/0001/0701-Enhance-console-tab-completions-for-brigadier-comman.patch diff --git a/patches/server-unmapped/0702-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/patches/server-unmapped-old/0001/0702-Fix-PlayerItemConsumeEvent-cancelling-properly.patch similarity index 100% rename from patches/server-unmapped/0702-Fix-PlayerItemConsumeEvent-cancelling-properly.patch rename to patches/server-unmapped-old/0001/0702-Fix-PlayerItemConsumeEvent-cancelling-properly.patch diff --git a/patches/server-unmapped/0703-Validate-bungee-forwarded-hostname.patch b/patches/server-unmapped-old/0001/0703-Validate-bungee-forwarded-hostname.patch similarity index 100% rename from patches/server-unmapped/0703-Validate-bungee-forwarded-hostname.patch rename to patches/server-unmapped-old/0001/0703-Validate-bungee-forwarded-hostname.patch diff --git a/patches/server-unmapped/0704-don-t-throw-when-loading-invalid-TEs.patch b/patches/server-unmapped-old/0001/0704-don-t-throw-when-loading-invalid-TEs.patch similarity index 100% rename from patches/server-unmapped/0704-don-t-throw-when-loading-invalid-TEs.patch rename to patches/server-unmapped-old/0001/0704-don-t-throw-when-loading-invalid-TEs.patch diff --git a/patches/server-unmapped/0705-Set-area-affect-cloud-rotation.patch b/patches/server-unmapped-old/0001/0705-Set-area-affect-cloud-rotation.patch similarity index 100% rename from patches/server-unmapped/0705-Set-area-affect-cloud-rotation.patch rename to patches/server-unmapped-old/0001/0705-Set-area-affect-cloud-rotation.patch diff --git a/patches/server-unmapped/0706-add-isDeeplySleeping-to-HumanEntity.patch b/patches/server-unmapped-old/0001/0706-add-isDeeplySleeping-to-HumanEntity.patch similarity index 100% rename from patches/server-unmapped/0706-add-isDeeplySleeping-to-HumanEntity.patch rename to patches/server-unmapped-old/0001/0706-add-isDeeplySleeping-to-HumanEntity.patch diff --git a/patches/server-unmapped/0707-Fix-duplicating-give-items-on-item-drop-cancel.patch b/patches/server-unmapped-old/0001/0707-Fix-duplicating-give-items-on-item-drop-cancel.patch similarity index 100% rename from patches/server-unmapped/0707-Fix-duplicating-give-items-on-item-drop-cancel.patch rename to patches/server-unmapped-old/0001/0707-Fix-duplicating-give-items-on-item-drop-cancel.patch diff --git a/patches/server-unmapped/0708-add-consumeFuel-to-FurnaceBurnEvent.patch b/patches/server-unmapped-old/0001/0708-add-consumeFuel-to-FurnaceBurnEvent.patch similarity index 100% rename from patches/server-unmapped/0708-add-consumeFuel-to-FurnaceBurnEvent.patch rename to patches/server-unmapped-old/0001/0708-add-consumeFuel-to-FurnaceBurnEvent.patch diff --git a/patches/server-unmapped/0709-add-get-set-drop-chance-to-EntityEquipment.patch b/patches/server-unmapped-old/0001/0709-add-get-set-drop-chance-to-EntityEquipment.patch similarity index 100% rename from patches/server-unmapped/0709-add-get-set-drop-chance-to-EntityEquipment.patch rename to patches/server-unmapped-old/0001/0709-add-get-set-drop-chance-to-EntityEquipment.patch diff --git a/patches/server-unmapped/0710-fix-PigZombieAngerEvent-cancellation.patch b/patches/server-unmapped-old/0001/0710-fix-PigZombieAngerEvent-cancellation.patch similarity index 100% rename from patches/server-unmapped/0710-fix-PigZombieAngerEvent-cancellation.patch rename to patches/server-unmapped-old/0001/0710-fix-PigZombieAngerEvent-cancellation.patch diff --git a/patches/server-unmapped/0711-Fix-checkReach-check-for-Shulker-boxes.patch b/patches/server-unmapped-old/0001/0711-Fix-checkReach-check-for-Shulker-boxes.patch similarity index 100% rename from patches/server-unmapped/0711-Fix-checkReach-check-for-Shulker-boxes.patch rename to patches/server-unmapped-old/0001/0711-Fix-checkReach-check-for-Shulker-boxes.patch diff --git a/patches/server-unmapped/0712-fix-PlayerItemHeldEvent-firing-twice.patch b/patches/server-unmapped-old/0001/0712-fix-PlayerItemHeldEvent-firing-twice.patch similarity index 100% rename from patches/server-unmapped/0712-fix-PlayerItemHeldEvent-firing-twice.patch rename to patches/server-unmapped-old/0001/0712-fix-PlayerItemHeldEvent-firing-twice.patch diff --git a/patches/server-unmapped/0713-Added-PlayerDeepSleepEvent.patch b/patches/server-unmapped-old/0001/0713-Added-PlayerDeepSleepEvent.patch similarity index 100% rename from patches/server-unmapped/0713-Added-PlayerDeepSleepEvent.patch rename to patches/server-unmapped-old/0001/0713-Added-PlayerDeepSleepEvent.patch diff --git a/patches/server-unmapped/0714-More-World-API.patch b/patches/server-unmapped-old/0001/0714-More-World-API.patch similarity index 100% rename from patches/server-unmapped/0714-More-World-API.patch rename to patches/server-unmapped-old/0001/0714-More-World-API.patch diff --git a/patches/server-unmapped/0715-Added-PlayerBedFailEnterEvent.patch b/patches/server-unmapped-old/0001/0715-Added-PlayerBedFailEnterEvent.patch similarity index 100% rename from patches/server-unmapped/0715-Added-PlayerBedFailEnterEvent.patch rename to patches/server-unmapped-old/0001/0715-Added-PlayerBedFailEnterEvent.patch diff --git a/patches/server-unmapped/0001/0001-McDev-imports.patch b/patches/server-unmapped/0001/0001-McDev-imports.patch new file mode 100644 index 0000000000..874364aeb2 --- /dev/null +++ b/patches/server-unmapped/0001/0001-McDev-imports.patch @@ -0,0 +1,35345 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Initial +Date: Fri, 25 Jun 2021 01:03:18 -0500 +Subject: [PATCH] McDev imports + + +diff --git a/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java b/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a3ab666b5fa89aad7ee167d9aeff2f62019a4a78 +--- /dev/null ++++ b/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java +@@ -0,0 +1,95 @@ ++package com.mojang.authlib.yggdrasil; ++ ++import com.google.common.base.Strings; ++import com.google.common.collect.Iterables; ++import com.google.common.collect.Sets; ++import com.mojang.authlib.Agent; ++import com.mojang.authlib.Environment; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.GameProfileRepository; ++import com.mojang.authlib.HttpAuthenticationService; ++import com.mojang.authlib.ProfileLookupCallback; ++import com.mojang.authlib.exceptions.AuthenticationException; ++import com.mojang.authlib.yggdrasil.response.ProfileSearchResultsResponse; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++import java.util.List; ++import java.util.Set; ++ ++public class YggdrasilGameProfileRepository implements GameProfileRepository { ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final String searchPageUrl; ++ private static final int ENTRIES_PER_PAGE = 2; ++ private static final int MAX_FAIL_COUNT = 3; ++ private static final int DELAY_BETWEEN_PAGES = 100; ++ private static final int DELAY_BETWEEN_FAILURES = 750; ++ ++ private final YggdrasilAuthenticationService authenticationService; ++ ++ public YggdrasilGameProfileRepository(final YggdrasilAuthenticationService authenticationService, final Environment environment) { ++ this.authenticationService = authenticationService; ++ searchPageUrl = environment.getAccountsHost() + "/profiles/"; ++ } ++ ++ @Override ++ public void findProfilesByNames(final String[] names, final Agent agent, final ProfileLookupCallback callback) { ++ final Set criteria = Sets.newHashSet(); ++ ++ for (final String name : names) { ++ if (!Strings.isNullOrEmpty(name)) { ++ criteria.add(name.toLowerCase()); ++ } ++ } ++ ++ final int page = 0; ++ ++ for (final List request : Iterables.partition(criteria, ENTRIES_PER_PAGE)) { ++ int failCount = 0; ++ boolean failed; ++ ++ do { ++ failed = false; ++ ++ try { ++ final ProfileSearchResultsResponse response = authenticationService.makeRequest(HttpAuthenticationService.constantURL(searchPageUrl + agent.getName().toLowerCase()), request, ProfileSearchResultsResponse.class); ++ failCount = 0; ++ ++ LOGGER.debug("Page {} returned {} results, parsing", page, response.getProfiles().length); ++ ++ final Set missing = Sets.newHashSet(request); ++ for (final GameProfile profile : response.getProfiles()) { ++ LOGGER.debug("Successfully looked up profile {}", profile); ++ missing.remove(profile.getName().toLowerCase()); ++ callback.onProfileLookupSucceeded(profile); ++ } ++ ++ for (final String name : missing) { ++ LOGGER.debug("Couldn't find profile {}", name); ++ callback.onProfileLookupFailed(new GameProfile(null, name), new ProfileNotFoundException("Server did not find the requested profile")); ++ } ++ ++ try { ++ Thread.sleep(DELAY_BETWEEN_PAGES); ++ } catch (final InterruptedException ignored) { ++ } ++ } catch (final AuthenticationException e) { ++ failCount++; ++ ++ if (failCount == MAX_FAIL_COUNT) { ++ for (final String name : request) { ++ LOGGER.debug("Couldn't find profile {} because of a server error", name); ++ callback.onProfileLookupFailed(new GameProfile(null, name), e); ++ } ++ } else { ++ try { ++ Thread.sleep(DELAY_BETWEEN_FAILURES); ++ } catch (final InterruptedException ignored) { ++ } ++ failed = true; ++ } ++ } ++ } while (failed); ++ } ++ } ++} +diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..103576715ef6ae4df4b216ae9ae31b6fb1086bd5 +--- /dev/null ++++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +@@ -0,0 +1,705 @@ ++// Copyright (c) Microsoft Corporation. All rights reserved. ++// Licensed under the MIT license. ++ ++package com.mojang.brigadier; ++ ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import com.mojang.brigadier.context.CommandContext; ++import com.mojang.brigadier.context.CommandContextBuilder; ++import com.mojang.brigadier.context.SuggestionContext; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import com.mojang.brigadier.suggestion.Suggestions; ++import com.mojang.brigadier.suggestion.SuggestionsBuilder; ++import com.mojang.brigadier.tree.CommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import com.mojang.brigadier.tree.RootCommandNode; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.LinkedHashMap; ++import java.util.LinkedHashSet; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.concurrent.CompletableFuture; ++import java.util.function.Predicate; ++import java.util.stream.Collectors; ++ ++ ++/** ++ * The core command dispatcher, for registering, parsing, and executing commands. ++ * ++ * @param a custom "source" type, such as a user or originator of a command ++ */ ++public class CommandDispatcher { ++ /** ++ * The string required to separate individual arguments in an input string ++ * ++ * @see #ARGUMENT_SEPARATOR_CHAR ++ */ ++ public static final String ARGUMENT_SEPARATOR = " "; ++ ++ /** ++ * The char required to separate individual arguments in an input string ++ * ++ * @see #ARGUMENT_SEPARATOR ++ */ ++ public static final char ARGUMENT_SEPARATOR_CHAR = ' '; ++ ++ private static final String USAGE_OPTIONAL_OPEN = "["; ++ private static final String USAGE_OPTIONAL_CLOSE = "]"; ++ private static final String USAGE_REQUIRED_OPEN = "("; ++ private static final String USAGE_REQUIRED_CLOSE = ")"; ++ private static final String USAGE_OR = "|"; ++ ++ private final RootCommandNode root; ++ ++ private final Predicate> hasCommand = new Predicate>() { ++ @Override ++ public boolean test(final CommandNode input) { ++ return input != null && (input.getCommand() != null || input.getChildren().stream().anyMatch(hasCommand)); ++ } ++ }; ++ private ResultConsumer consumer = (c, s, r) -> { ++ }; ++ ++ /** ++ * Create a new {@link CommandDispatcher} with the specified root node. ++ * ++ *

This is often useful to copy existing or pre-defined command trees.

++ * ++ * @param root the existing {@link RootCommandNode} to use as the basis for this tree ++ */ ++ public CommandDispatcher(final RootCommandNode root) { ++ this.root = root; ++ } ++ ++ /** ++ * Creates a new {@link CommandDispatcher} with an empty command tree. ++ */ ++ public CommandDispatcher() { ++ this(new RootCommandNode<>()); ++ } ++ ++ /** ++ * Utility method for registering new commands. ++ * ++ *

This is a shortcut for calling {@link RootCommandNode#addChild(CommandNode)} after building the provided {@code command}.

++ * ++ *

As {@link RootCommandNode} can only hold literals, this method will only allow literal arguments.

++ * ++ * @param command a literal argument builder to add to this command tree ++ * @return the node added to this tree ++ */ ++ public LiteralCommandNode register(final LiteralArgumentBuilder command) { ++ final LiteralCommandNode build = command.build(); ++ root.addChild(build); ++ return build; ++ } ++ ++ /** ++ * Sets a callback to be informed of the result of every command. ++ * ++ * @param consumer the new result consumer to be called ++ */ ++ public void setConsumer(final ResultConsumer consumer) { ++ this.consumer = consumer; ++ } ++ ++ /** ++ * Parses and executes a given command. ++ * ++ *

This is a shortcut to first {@link #parse(StringReader, Object)} and then {@link #execute(ParseResults)}.

++ * ++ *

It is recommended to parse and execute as separate steps, as parsing is often the most expensive step, and easiest to cache.

++ * ++ *

If this command returns a value, then it successfully executed something. If it could not parse the command, or the execution was a failure, ++ * then an exception will be thrown. Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException} ++ * may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend ++ * entirely on what command was performed.

++ * ++ *

If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'. ++ * A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into ++ * 'amount of successful commands executes'.

++ * ++ *

After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)} ++ * will be notified of the result and success of the command. You can use that method to gather more meaningful ++ * results than this method will return, especially when a command forks.

++ * ++ * @param input a command string to parse & execute ++ * @param source a custom "source" object, usually representing the originator of this command ++ * @return a numeric result from a "command" that was performed ++ * @throws CommandSyntaxException if the command failed to parse or execute ++ * @throws RuntimeException if the command failed to execute and was not handled gracefully ++ * @see #parse(String, Object) ++ * @see #parse(StringReader, Object) ++ * @see #execute(ParseResults) ++ * @see #execute(StringReader, Object) ++ */ ++ public int execute(final String input, final S source) throws CommandSyntaxException { ++ return execute(new StringReader(input), source); ++ } ++ ++ /** ++ * Parses and executes a given command. ++ * ++ *

This is a shortcut to first {@link #parse(StringReader, Object)} and then {@link #execute(ParseResults)}.

++ * ++ *

It is recommended to parse and execute as separate steps, as parsing is often the most expensive step, and easiest to cache.

++ * ++ *

If this command returns a value, then it successfully executed something. If it could not parse the command, or the execution was a failure, ++ * then an exception will be thrown. Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException} ++ * may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend ++ * entirely on what command was performed.

++ * ++ *

If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'. ++ * A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into ++ * 'amount of successful commands executes'.

++ * ++ *

After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)} ++ * will be notified of the result and success of the command. You can use that method to gather more meaningful ++ * results than this method will return, especially when a command forks.

++ * ++ * @param input a command string to parse & execute ++ * @param source a custom "source" object, usually representing the originator of this command ++ * @return a numeric result from a "command" that was performed ++ * @throws CommandSyntaxException if the command failed to parse or execute ++ * @throws RuntimeException if the command failed to execute and was not handled gracefully ++ * @see #parse(String, Object) ++ * @see #parse(StringReader, Object) ++ * @see #execute(ParseResults) ++ * @see #execute(String, Object) ++ */ ++ public int execute(final StringReader input, final S source) throws CommandSyntaxException { ++ final ParseResults parse = parse(input, source); ++ return execute(parse); ++ } ++ ++ /** ++ * Executes a given pre-parsed command. ++ * ++ *

If this command returns a value, then it successfully executed something. If the execution was a failure, ++ * then an exception will be thrown. ++ * Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException} ++ * may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend ++ * entirely on what command was performed.

++ * ++ *

If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'. ++ * A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into ++ * 'amount of successful commands executes'.

++ * ++ *

After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)} ++ * will be notified of the result and success of the command. You can use that method to gather more meaningful ++ * results than this method will return, especially when a command forks.

++ * ++ * @param parse the result of a successful {@link #parse(StringReader, Object)} ++ * @return a numeric result from a "command" that was performed. ++ * @throws CommandSyntaxException if the command failed to parse or execute ++ * @throws RuntimeException if the command failed to execute and was not handled gracefully ++ * @see #parse(String, Object) ++ * @see #parse(StringReader, Object) ++ * @see #execute(String, Object) ++ * @see #execute(StringReader, Object) ++ */ ++ public int execute(final ParseResults parse) throws CommandSyntaxException { ++ if (parse.getReader().canRead()) { ++ if (parse.getExceptions().size() == 1) { ++ throw parse.getExceptions().values().iterator().next(); ++ } else if (parse.getContext().getRange().isEmpty()) { ++ throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parse.getReader()); ++ } else { ++ throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(parse.getReader()); ++ } ++ } ++ ++ int result = 0; ++ int successfulForks = 0; ++ boolean forked = false; ++ boolean foundCommand = false; ++ final String command = parse.getReader().getString(); ++ final CommandContext original = parse.getContext().build(command); ++ List> contexts = Collections.singletonList(original); ++ ArrayList> next = null; ++ ++ while (contexts != null) { ++ final int size = contexts.size(); ++ for (int i = 0; i < size; i++) { ++ final CommandContext context = contexts.get(i); ++ final CommandContext child = context.getChild(); ++ if (child != null) { ++ forked |= context.isForked(); ++ if (child.hasNodes()) { ++ foundCommand = true; ++ final RedirectModifier modifier = context.getRedirectModifier(); ++ if (modifier == null) { ++ if (next == null) { ++ next = new ArrayList<>(1); ++ } ++ next.add(child.copyFor(context.getSource())); ++ } else { ++ try { ++ final Collection results = modifier.apply(context); ++ if (!results.isEmpty()) { ++ if (next == null) { ++ next = new ArrayList<>(results.size()); ++ } ++ for (final S source : results) { ++ next.add(child.copyFor(source)); ++ } ++ } ++ } catch (final CommandSyntaxException ex) { ++ consumer.onCommandComplete(context, false, 0); ++ if (!forked) { ++ throw ex; ++ } ++ } ++ } ++ } ++ } else if (context.getCommand() != null) { ++ foundCommand = true; ++ try { ++ final int value = context.getCommand().run(context); ++ result += value; ++ consumer.onCommandComplete(context, true, value); ++ successfulForks++; ++ } catch (final CommandSyntaxException ex) { ++ consumer.onCommandComplete(context, false, 0); ++ if (!forked) { ++ throw ex; ++ } ++ } ++ } ++ } ++ ++ contexts = next; ++ next = null; ++ } ++ ++ if (!foundCommand) { ++ consumer.onCommandComplete(original, false, 0); ++ throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parse.getReader()); ++ } ++ ++ return forked ? successfulForks : result; ++ } ++ ++ /** ++ * Parses a given command. ++ * ++ *

The result of this method can be cached, and it is advised to do so where appropriate. Parsing is often the ++ * most expensive step, and this allows you to essentially "precompile" a command if it will be ran often.

++ * ++ *

If the command passes through a node that is {@link CommandNode#isFork()} then the resulting context will be marked as 'forked'. ++ * Forked contexts may contain child contexts, which may be modified by the {@link RedirectModifier} attached to the fork.

++ * ++ *

Parsing a command can never fail, you will always be provided with a new {@link ParseResults}. ++ * However, that does not mean that it will always parse into a valid command. You should inspect the returned results ++ * to check for validity. If its {@link ParseResults#getReader()} {@link StringReader#canRead()} then it did not finish ++ * parsing successfully. You can use that position as an indicator to the user where the command stopped being valid. ++ * You may inspect {@link ParseResults#getExceptions()} if you know the parse failed, as it will explain why it could ++ * not find any valid commands. It may contain multiple exceptions, one for each "potential node" that it could have visited, ++ * explaining why it did not go down that node.

++ * ++ *

When you eventually call {@link #execute(ParseResults)} with the result of this method, the above error checking ++ * will occur. You only need to inspect it yourself if you wish to handle that yourself.

++ * ++ * @param command a command string to parse ++ * @param source a custom "source" object, usually representing the originator of this command ++ * @return the result of parsing this command ++ * @see #parse(StringReader, Object) ++ * @see #execute(ParseResults) ++ * @see #execute(String, Object) ++ */ ++ public ParseResults parse(final String command, final S source) { ++ return parse(new StringReader(command), source); ++ } ++ ++ /** ++ * Parses a given command. ++ * ++ *

The result of this method can be cached, and it is advised to do so where appropriate. Parsing is often the ++ * most expensive step, and this allows you to essentially "precompile" a command if it will be ran often.

++ * ++ *

If the command passes through a node that is {@link CommandNode#isFork()} then the resulting context will be marked as 'forked'. ++ * Forked contexts may contain child contexts, which may be modified by the {@link RedirectModifier} attached to the fork.

++ * ++ *

Parsing a command can never fail, you will always be provided with a new {@link ParseResults}. ++ * However, that does not mean that it will always parse into a valid command. You should inspect the returned results ++ * to check for validity. If its {@link ParseResults#getReader()} {@link StringReader#canRead()} then it did not finish ++ * parsing successfully. You can use that position as an indicator to the user where the command stopped being valid. ++ * You may inspect {@link ParseResults#getExceptions()} if you know the parse failed, as it will explain why it could ++ * not find any valid commands. It may contain multiple exceptions, one for each "potential node" that it could have visited, ++ * explaining why it did not go down that node.

++ * ++ *

When you eventually call {@link #execute(ParseResults)} with the result of this method, the above error checking ++ * will occur. You only need to inspect it yourself if you wish to handle that yourself.

++ * ++ * @param command a command string to parse ++ * @param source a custom "source" object, usually representing the originator of this command ++ * @return the result of parsing this command ++ * @see #parse(String, Object) ++ * @see #execute(ParseResults) ++ * @see #execute(String, Object) ++ */ ++ public ParseResults parse(final StringReader command, final S source) { ++ final CommandContextBuilder context = new CommandContextBuilder<>(this, source, root, command.getCursor()); ++ return parseNodes(root, command, context); ++ } ++ ++ private ParseResults parseNodes(final CommandNode node, final StringReader originalReader, final CommandContextBuilder contextSoFar) { ++ final S source = contextSoFar.getSource(); ++ Map, CommandSyntaxException> errors = null; ++ List> potentials = null; ++ final int cursor = originalReader.getCursor(); ++ ++ for (final CommandNode child : node.getRelevantNodes(originalReader)) { ++ if (!child.canUse(source)) { ++ continue; ++ } ++ final CommandContextBuilder context = contextSoFar.copy(); ++ final StringReader reader = new StringReader(originalReader); ++ try { ++ try { ++ child.parse(reader, context); ++ } catch (final RuntimeException ex) { ++ throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().createWithContext(reader, ex.getMessage()); ++ } ++ if (reader.canRead()) { ++ if (reader.peek() != ARGUMENT_SEPARATOR_CHAR) { ++ throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherExpectedArgumentSeparator().createWithContext(reader); ++ } ++ } ++ } catch (final CommandSyntaxException ex) { ++ if (errors == null) { ++ errors = new LinkedHashMap<>(); ++ } ++ errors.put(child, ex); ++ reader.setCursor(cursor); ++ continue; ++ } ++ ++ context.withCommand(child.getCommand()); ++ if (reader.canRead(child.getRedirect() == null ? 2 : 1)) { ++ reader.skip(); ++ if (child.getRedirect() != null) { ++ final CommandContextBuilder childContext = new CommandContextBuilder<>(this, source, child.getRedirect(), reader.getCursor()); ++ final ParseResults parse = parseNodes(child.getRedirect(), reader, childContext); ++ context.withChild(parse.getContext()); ++ return new ParseResults<>(context, parse.getReader(), parse.getExceptions()); ++ } else { ++ final ParseResults parse = parseNodes(child, reader, context); ++ if (potentials == null) { ++ potentials = new ArrayList<>(1); ++ } ++ potentials.add(parse); ++ } ++ } else { ++ if (potentials == null) { ++ potentials = new ArrayList<>(1); ++ } ++ potentials.add(new ParseResults<>(context, reader, Collections.emptyMap())); ++ } ++ } ++ ++ if (potentials != null) { ++ if (potentials.size() > 1) { ++ potentials.sort((a, b) -> { ++ if (!a.getReader().canRead() && b.getReader().canRead()) { ++ return -1; ++ } ++ if (a.getReader().canRead() && !b.getReader().canRead()) { ++ return 1; ++ } ++ if (a.getExceptions().isEmpty() && !b.getExceptions().isEmpty()) { ++ return -1; ++ } ++ if (!a.getExceptions().isEmpty() && b.getExceptions().isEmpty()) { ++ return 1; ++ } ++ return 0; ++ }); ++ } ++ return potentials.get(0); ++ } ++ ++ return new ParseResults<>(contextSoFar, originalReader, errors == null ? Collections.emptyMap() : errors); ++ } ++ ++ /** ++ * Gets all possible executable commands following the given node. ++ * ++ *

You may use {@link #getRoot()} as a target to get all usage data for the entire command tree.

++ * ++ *

The returned syntax will be in "simple" form: {@code } and {@code literal}. "Optional" nodes will be ++ * listed as multiple entries: the parent node, and the child nodes. ++ * For example, a required literal "foo" followed by an optional param "int" will be two nodes:

++ *
    ++ *
  • {@code foo}
  • ++ *
  • {@code foo }
  • ++ *
++ * ++ *

The path to the specified node will not be prepended to the output, as there can theoretically be many ++ * ways to reach a given node. It will only give you paths relative to the specified node, not absolute from root.

++ * ++ * @param node target node to get child usage strings for ++ * @param source a custom "source" object, usually representing the originator of this command ++ * @param restricted if true, commands that the {@code source} cannot access will not be mentioned ++ * @return array of full usage strings under the target node ++ */ ++ public String[] getAllUsage(final CommandNode node, final S source, final boolean restricted) { ++ final ArrayList result = new ArrayList<>(); ++ getAllUsage(node, source, result, "", restricted); ++ return result.toArray(new String[result.size()]); ++ } ++ ++ private void getAllUsage(final CommandNode node, final S source, final ArrayList result, final String prefix, final boolean restricted) { ++ if (restricted && !node.canUse(source)) { ++ return; ++ } ++ ++ if (node.getCommand() != null) { ++ result.add(prefix); ++ } ++ ++ if (node.getRedirect() != null) { ++ final String redirect = node.getRedirect() == root ? "..." : "-> " + node.getRedirect().getUsageText(); ++ result.add(prefix.isEmpty() ? node.getUsageText() + ARGUMENT_SEPARATOR + redirect : prefix + ARGUMENT_SEPARATOR + redirect); ++ } else if (!node.getChildren().isEmpty()) { ++ for (final CommandNode child : node.getChildren()) { ++ getAllUsage(child, source, result, prefix.isEmpty() ? child.getUsageText() : prefix + ARGUMENT_SEPARATOR + child.getUsageText(), restricted); ++ } ++ } ++ } ++ ++ /** ++ * Gets the possible executable commands from a specified node. ++ * ++ *

You may use {@link #getRoot()} as a target to get usage data for the entire command tree.

++ * ++ *

The returned syntax will be in "smart" form: {@code }, {@code literal}, {@code [optional]} and {@code (either|or)}. ++ * These forms may be mixed and matched to provide as much information about the child nodes as it can, without being too verbose. ++ * For example, a required literal "foo" followed by an optional param "int" can be compressed into one string:

++ *
    ++ *
  • {@code foo []}
  • ++ *
++ * ++ *

The path to the specified node will not be prepended to the output, as there can theoretically be many ++ * ways to reach a given node. It will only give you paths relative to the specified node, not absolute from root.

++ * ++ *

The returned usage will be restricted to only commands that the provided {@code source} can use.

++ * ++ * @param node target node to get child usage strings for ++ * @param source a custom "source" object, usually representing the originator of this command ++ * @return array of full usage strings under the target node ++ */ ++ public Map, String> getSmartUsage(final CommandNode node, final S source) { ++ final Map, String> result = new LinkedHashMap<>(); ++ ++ final boolean optional = node.getCommand() != null; ++ for (final CommandNode child : node.getChildren()) { ++ final String usage = getSmartUsage(child, source, optional, false); ++ if (usage != null) { ++ result.put(child, usage); ++ } ++ } ++ return result; ++ } ++ ++ private String getSmartUsage(final CommandNode node, final S source, final boolean optional, final boolean deep) { ++ if (!node.canUse(source)) { ++ return null; ++ } ++ ++ final String self = optional ? USAGE_OPTIONAL_OPEN + node.getUsageText() + USAGE_OPTIONAL_CLOSE : node.getUsageText(); ++ final boolean childOptional = node.getCommand() != null; ++ final String open = childOptional ? USAGE_OPTIONAL_OPEN : USAGE_REQUIRED_OPEN; ++ final String close = childOptional ? USAGE_OPTIONAL_CLOSE : USAGE_REQUIRED_CLOSE; ++ ++ if (!deep) { ++ if (node.getRedirect() != null) { ++ final String redirect = node.getRedirect() == root ? "..." : "-> " + node.getRedirect().getUsageText(); ++ return self + ARGUMENT_SEPARATOR + redirect; ++ } else { ++ final Collection> children = node.getChildren().stream().filter(c -> c.canUse(source)).collect(Collectors.toList()); ++ if (children.size() == 1) { ++ final String usage = getSmartUsage(children.iterator().next(), source, childOptional, childOptional); ++ if (usage != null) { ++ return self + ARGUMENT_SEPARATOR + usage; ++ } ++ } else if (children.size() > 1) { ++ final Set childUsage = new LinkedHashSet<>(); ++ for (final CommandNode child : children) { ++ final String usage = getSmartUsage(child, source, childOptional, true); ++ if (usage != null) { ++ childUsage.add(usage); ++ } ++ } ++ if (childUsage.size() == 1) { ++ final String usage = childUsage.iterator().next(); ++ return self + ARGUMENT_SEPARATOR + (childOptional ? USAGE_OPTIONAL_OPEN + usage + USAGE_OPTIONAL_CLOSE : usage); ++ } else if (childUsage.size() > 1) { ++ final StringBuilder builder = new StringBuilder(open); ++ int count = 0; ++ for (final CommandNode child : children) { ++ if (count > 0) { ++ builder.append(USAGE_OR); ++ } ++ builder.append(child.getUsageText()); ++ count++; ++ } ++ if (count > 0) { ++ builder.append(close); ++ return self + ARGUMENT_SEPARATOR + builder.toString(); ++ } ++ } ++ } ++ } ++ } ++ ++ return self; ++ } ++ ++ /** ++ * Gets suggestions for a parsed input string on what comes next. ++ * ++ *

As it is ultimately up to custom argument types to provide suggestions, it may be an asynchronous operation, ++ * for example getting in-game data or player names etc. As such, this method returns a future and no guarantees ++ * are made to when or how the future completes.

++ * ++ *

The suggestions provided will be in the context of the end of the parsed input string, but may suggest ++ * new or replacement strings for earlier in the input string. For example, if the end of the string was ++ * {@code foobar} but an argument preferred it to be {@code minecraft:foobar}, it will suggest a replacement for that ++ * whole segment of the input.

++ * ++ * @param parse the result of a {@link #parse(StringReader, Object)} ++ * @return a future that will eventually resolve into a {@link Suggestions} object ++ */ ++ public CompletableFuture getCompletionSuggestions(final ParseResults parse) { ++ return getCompletionSuggestions(parse, parse.getReader().getTotalLength()); ++ } ++ ++ public CompletableFuture getCompletionSuggestions(final ParseResults parse, int cursor) { ++ final CommandContextBuilder context = parse.getContext(); ++ ++ final SuggestionContext nodeBeforeCursor = context.findSuggestionContext(cursor); ++ final CommandNode parent = nodeBeforeCursor.parent; ++ final int start = Math.min(nodeBeforeCursor.startPos, cursor); ++ ++ final String fullInput = parse.getReader().getString(); ++ final String truncatedInput = fullInput.substring(0, cursor); ++ @SuppressWarnings("unchecked") final CompletableFuture[] futures = new CompletableFuture[parent.getChildren().size()]; ++ int i = 0; ++ for (final CommandNode node : parent.getChildren()) { ++ CompletableFuture future = Suggestions.empty(); ++ try { ++ future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, start)); ++ } catch (final CommandSyntaxException ignored) { ++ } ++ futures[i++] = future; ++ } ++ ++ final CompletableFuture result = new CompletableFuture<>(); ++ CompletableFuture.allOf(futures).thenRun(() -> { ++ final List suggestions = new ArrayList<>(); ++ for (final CompletableFuture future : futures) { ++ suggestions.add(future.join()); ++ } ++ result.complete(Suggestions.merge(fullInput, suggestions)); ++ }); ++ ++ return result; ++ } ++ ++ /** ++ * Gets the root of this command tree. ++ * ++ *

This is often useful as a target of a {@link com.mojang.brigadier.builder.ArgumentBuilder#redirect(CommandNode)}, ++ * {@link #getAllUsage(CommandNode, Object, boolean)} or {@link #getSmartUsage(CommandNode, Object)}. ++ * You may also use it to clone the command tree via {@link #CommandDispatcher(RootCommandNode)}.

++ * ++ * @return root of the command tree ++ */ ++ public RootCommandNode getRoot() { ++ return root; ++ } ++ ++ /** ++ * Finds a valid path to a given node on the command tree. ++ * ++ *

There may theoretically be multiple paths to a node on the tree, especially with the use of forking or redirecting. ++ * As such, this method makes no guarantees about which path it finds. It will not look at forks or redirects, ++ * and find the first instance of the target node on the tree.

++ * ++ *

The only guarantee made is that for the same command tree and the same version of this library, the result of ++ * this method will always be a valid input for {@link #findNode(Collection)}, which should return the same node ++ * as provided to this method.

++ * ++ * @param target the target node you are finding a path for ++ * @return a path to the resulting node, or an empty list if it was not found ++ */ ++ public Collection getPath(final CommandNode target) { ++ final List>> nodes = new ArrayList<>(); ++ addPaths(root, nodes, new ArrayList<>()); ++ ++ for (final List> list : nodes) { ++ if (list.get(list.size() - 1) == target) { ++ final List result = new ArrayList<>(list.size()); ++ for (final CommandNode node : list) { ++ if (node != root) { ++ result.add(node.getName()); ++ } ++ } ++ return result; ++ } ++ } ++ ++ return Collections.emptyList(); ++ } ++ ++ /** ++ * Finds a node by its path ++ * ++ *

Paths may be generated with {@link #getPath(CommandNode)}, and are guaranteed (for the same tree, and the ++ * same version of this library) to always produce the same valid node by this method.

++ * ++ *

If a node could not be found at the specified path, then {@code null} will be returned.

++ * ++ * @param path a generated path to a node ++ * @return the node at the given path, or null if not found ++ */ ++ public CommandNode findNode(final Collection path) { ++ CommandNode node = root; ++ for (final String name : path) { ++ node = node.getChild(name); ++ if (node == null) { ++ return null; ++ } ++ } ++ return node; ++ } ++ ++ /** ++ * Scans the command tree for potential ambiguous commands. ++ * ++ *

This is a shortcut for {@link CommandNode#findAmbiguities(AmbiguityConsumer)} on {@link #getRoot()}.

++ * ++ *

Ambiguities are detected by testing every {@link CommandNode#getExamples()} on one node verses every sibling ++ * node. This is not fool proof, and relies a lot on the providers of the used argument types to give good examples.

++ * ++ * @param consumer a callback to be notified of potential ambiguities ++ */ ++ public void findAmbiguities(final AmbiguityConsumer consumer) { ++ root.findAmbiguities(consumer); ++ } ++ ++ private void addPaths(final CommandNode node, final List>> result, final List> parents) { ++ final List> current = new ArrayList<>(parents); ++ current.add(node); ++ result.add(current); ++ ++ for (final CommandNode child : node.getChildren()) { ++ addPaths(child, result, current); ++ } ++ } ++} +diff --git a/src/main/java/com/mojang/brigadier/arguments/BoolArgumentType.java b/src/main/java/com/mojang/brigadier/arguments/BoolArgumentType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cb993ca102402d9c19ea9fa04e5db09c21205896 +--- /dev/null ++++ b/src/main/java/com/mojang/brigadier/arguments/BoolArgumentType.java +@@ -0,0 +1,50 @@ ++// Copyright (c) Microsoft Corporation. All rights reserved. ++// Licensed under the MIT license. ++ ++package com.mojang.brigadier.arguments; ++ ++import com.mojang.brigadier.StringReader; ++import com.mojang.brigadier.context.CommandContext; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import com.mojang.brigadier.suggestion.Suggestions; ++import com.mojang.brigadier.suggestion.SuggestionsBuilder; ++ ++import java.util.Arrays; ++import java.util.Collection; ++import java.util.concurrent.CompletableFuture; ++ ++public class BoolArgumentType implements ArgumentType { ++ private static final Collection EXAMPLES = Arrays.asList("true", "false"); ++ ++ private BoolArgumentType() { ++ } ++ ++ public static BoolArgumentType bool() { ++ return new BoolArgumentType(); ++ } ++ ++ public static boolean getBool(final CommandContext context, final String name) { ++ return context.getArgument(name, Boolean.class); ++ } ++ ++ @Override ++ public Boolean parse(final StringReader reader) throws CommandSyntaxException { ++ return reader.readBoolean(); ++ } ++ ++ @Override ++ public CompletableFuture listSuggestions(final CommandContext context, final SuggestionsBuilder builder) { ++ if ("true".startsWith(builder.getRemaining().toLowerCase())) { ++ builder.suggest("true"); ++ } ++ if ("false".startsWith(builder.getRemaining().toLowerCase())) { ++ builder.suggest("false"); ++ } ++ return builder.buildFuture(); ++ } ++ ++ @Override ++ public Collection getExamples() { ++ return EXAMPLES; ++ } ++} +diff --git a/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java b/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bc0024adb804ac055a4f8afb7f85d00ec13931e9 +--- /dev/null ++++ b/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java +@@ -0,0 +1,83 @@ ++// Copyright (c) Microsoft Corporation. All rights reserved. ++// Licensed under the MIT license. ++ ++package com.mojang.brigadier.suggestion; ++ ++import com.mojang.brigadier.Message; ++import com.mojang.brigadier.context.StringRange; ++ ++import java.util.ArrayList; ++import java.util.List; ++import java.util.concurrent.CompletableFuture; ++ ++public class SuggestionsBuilder { ++ private final String input; ++ private final int start; ++ private final String remaining; ++ private final List result = new ArrayList<>(); ++ ++ public SuggestionsBuilder(final String input, final int start) { ++ this.input = input; ++ this.start = start; ++ this.remaining = input.substring(start); ++ } ++ ++ public String getInput() { ++ return input; ++ } ++ ++ public int getStart() { ++ return start; ++ } ++ ++ public String getRemaining() { ++ return remaining; ++ } ++ ++ public Suggestions build() { ++ return Suggestions.create(input, result); ++ } ++ ++ public CompletableFuture buildFuture() { ++ return CompletableFuture.completedFuture(build()); ++ } ++ ++ public SuggestionsBuilder suggest(final String text) { ++ if (text.equals(remaining)) { ++ return this; ++ } ++ result.add(new Suggestion(StringRange.between(start, input.length()), text)); ++ return this; ++ } ++ ++ public SuggestionsBuilder suggest(final String text, final Message tooltip) { ++ if (text.equals(remaining)) { ++ return this; ++ } ++ result.add(new Suggestion(StringRange.between(start, input.length()), text, tooltip)); ++ return this; ++ } ++ ++ public SuggestionsBuilder suggest(final int value) { ++ result.add(new IntegerSuggestion(StringRange.between(start, input.length()), value)); ++ return this; ++ } ++ ++ public SuggestionsBuilder suggest(final int value, final Message tooltip) { ++ result.add(new IntegerSuggestion(StringRange.between(start, input.length()), value, tooltip)); ++ return this; ++ } ++ ++ public SuggestionsBuilder add(final SuggestionsBuilder other) { ++ result.addAll(other.result); ++ return this; ++ } ++ ++ public SuggestionsBuilder createOffset(final int start) { ++ return new SuggestionsBuilder(input, start); ++ } ++ ++ public SuggestionsBuilder restart() { ++ return new SuggestionsBuilder(input, start); ++ } ++} +diff --git a/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java b/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7720578796e28d28e8c0c9aa40155cd205c17d54 +--- /dev/null ++++ b/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java +@@ -0,0 +1,129 @@ ++// Copyright (c) Microsoft Corporation. All rights reserved. ++// Licensed under the MIT license. ++ ++package com.mojang.brigadier.tree; ++ ++import com.mojang.brigadier.Command; ++import com.mojang.brigadier.RedirectModifier; ++import com.mojang.brigadier.StringReader; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import com.mojang.brigadier.context.CommandContext; ++import com.mojang.brigadier.context.CommandContextBuilder; ++import com.mojang.brigadier.context.StringRange; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import com.mojang.brigadier.suggestion.Suggestions; ++import com.mojang.brigadier.suggestion.SuggestionsBuilder; ++ ++import java.util.Collection; ++import java.util.Collections; ++import java.util.concurrent.CompletableFuture; ++import java.util.function.Predicate; ++ ++public class LiteralCommandNode extends CommandNode { ++ private final String literal; ++ ++ public LiteralCommandNode(final String literal, final Command command, final Predicate requirement, final CommandNode redirect, final RedirectModifier modifier, final boolean forks) { ++ super(command, requirement, redirect, modifier, forks); ++ this.literal = literal; ++ } ++ ++ public String getLiteral() { ++ return literal; ++ } ++ ++ @Override ++ public String getName() { ++ return literal; ++ } ++ ++ @Override ++ public void parse(final StringReader reader, final CommandContextBuilder contextBuilder) throws CommandSyntaxException { ++ final int start = reader.getCursor(); ++ final int end = parse(reader); ++ if (end > -1) { ++ contextBuilder.withNode(this, StringRange.between(start, end)); ++ return; ++ } ++ ++ throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect().createWithContext(reader, literal); ++ } ++ ++ private int parse(final StringReader reader) { ++ final int start = reader.getCursor(); ++ if (reader.canRead(literal.length())) { ++ final int end = start + literal.length(); ++ if (reader.getString().substring(start, end).equals(literal)) { ++ reader.setCursor(end); ++ if (!reader.canRead() || reader.peek() == ' ') { ++ return end; ++ } else { ++ reader.setCursor(start); ++ } ++ } ++ } ++ return -1; ++ } ++ ++ @Override ++ public CompletableFuture listSuggestions(final CommandContext context, final SuggestionsBuilder builder) { ++ if (literal.toLowerCase().startsWith(builder.getRemaining().toLowerCase())) { ++ return builder.suggest(literal).buildFuture(); ++ } else { ++ return Suggestions.empty(); ++ } ++ } ++ ++ @Override ++ public boolean isValidInput(final String input) { ++ return parse(new StringReader(input)) > -1; ++ } ++ ++ @Override ++ public boolean equals(final Object o) { ++ if (this == o) return true; ++ if (!(o instanceof LiteralCommandNode)) return false; ++ ++ final LiteralCommandNode that = (LiteralCommandNode) o; ++ ++ if (!literal.equals(that.literal)) return false; ++ return super.equals(o); ++ } ++ ++ @Override ++ public String getUsageText() { ++ return literal; ++ } ++ ++ @Override ++ public int hashCode() { ++ int result = literal.hashCode(); ++ result = 31 * result + super.hashCode(); ++ return result; ++ } ++ ++ @Override ++ public LiteralArgumentBuilder createBuilder() { ++ final LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal(this.literal); ++ builder.requires(getRequirement()); ++ builder.forward(getRedirect(), getRedirectModifier(), isFork()); ++ if (getCommand() != null) { ++ builder.executes(getCommand()); ++ } ++ return builder; ++ } ++ ++ @Override ++ protected String getSortedKey() { ++ return literal; ++ } ++ ++ @Override ++ public Collection getExamples() { ++ return Collections.singleton(literal); ++ } ++ ++ @Override ++ public String toString() { ++ return ""; ++ } ++} +diff --git a/src/main/java/com/mojang/datafixers/DataFixerBuilder.java b/src/main/java/com/mojang/datafixers/DataFixerBuilder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..edb77982d273e9492ab1a669ca1ad89da2ec3c3e +--- /dev/null ++++ b/src/main/java/com/mojang/datafixers/DataFixerBuilder.java +@@ -0,0 +1,84 @@ ++// Copyright (c) Microsoft Corporation. All rights reserved. ++// Licensed under the MIT license. ++package com.mojang.datafixers; ++ ++import com.google.common.collect.Lists; ++import com.mojang.datafixers.schemas.Schema; ++import com.mojang.datafixers.types.Type; ++import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap; ++import it.unimi.dsi.fastutil.ints.IntAVLTreeSet; ++import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator; ++import it.unimi.dsi.fastutil.ints.IntSortedSet; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++import java.util.ArrayList; ++import java.util.List; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.Executor; ++import java.util.function.BiFunction; ++ ++public class DataFixerBuilder { ++ private static final Logger LOGGER = LogManager.getLogger(); ++ ++ private final int dataVersion; ++ private final Int2ObjectSortedMap schemas = new Int2ObjectAVLTreeMap<>(); ++ private final List globalList = Lists.newArrayList(); ++ private final IntSortedSet fixerVersions = new IntAVLTreeSet(); ++ ++ public DataFixerBuilder(final int dataVersion) { ++ this.dataVersion = dataVersion; ++ } ++ ++ public Schema addSchema(final int version, final BiFunction factory) { ++ return addSchema(version, 0, factory); ++ } ++ ++ public Schema addSchema(final int version, final int subVersion, final BiFunction factory) { ++ final int key = DataFixUtils.makeKey(version, subVersion); ++ final Schema parent = schemas.isEmpty() ? null : schemas.get(DataFixerUpper.getLowestSchemaSameVersion(schemas, key - 1)); ++ final Schema schema = factory.apply(DataFixUtils.makeKey(version, subVersion), parent); ++ addSchema(schema); ++ return schema; ++ } ++ ++ public void addSchema(final Schema schema) { ++ schemas.put(schema.getVersionKey(), schema); ++ } ++ ++ public void addFixer(final DataFix fix) { ++ final int version = DataFixUtils.getVersion(fix.getVersionKey()); ++ ++ if (version > dataVersion) { ++ LOGGER.warn("Ignored fix registered for version: {} as the DataVersion of the game is: {}", version, dataVersion); ++ return; ++ } ++ ++ globalList.add(fix); ++ fixerVersions.add(fix.getVersionKey()); ++ } ++ ++ public DataFixer build(final Executor executor) { ++ final DataFixerUpper fixerUpper = new DataFixerUpper(new Int2ObjectAVLTreeMap<>(schemas), new ArrayList<>(globalList), new IntAVLTreeSet(fixerVersions)); ++ ++ final IntBidirectionalIterator iterator = fixerUpper.fixerVersions().iterator(); ++ while (iterator.hasNext()) { ++ final int versionKey = iterator.nextInt(); ++ final Schema schema = schemas.get(versionKey); ++ for (final String typeName : schema.types()) { ++ CompletableFuture.runAsync(() -> { ++ final Type dataType = schema.getType(() -> typeName); ++ final TypeRewriteRule rule = fixerUpper.getRule(DataFixUtils.getVersion(versionKey), dataVersion); ++ dataType.rewrite(rule, DataFixerUpper.OPTIMIZATION_RULE); ++ }, executor).exceptionally(e -> { ++ LOGGER.error("Unable to build datafixers", e); ++ Runtime.getRuntime().exit(1); ++ return null; ++ }); ++ } ++ } ++ ++ return fixerUpper; ++ } ++} +diff --git a/src/main/java/com/mojang/datafixers/util/Either.java b/src/main/java/com/mojang/datafixers/util/Either.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a90adac7bd7ebd423f480e9ae0f44cb9d521fa4f +--- /dev/null ++++ b/src/main/java/com/mojang/datafixers/util/Either.java +@@ -0,0 +1,244 @@ ++// Copyright (c) Microsoft Corporation. All rights reserved. ++// Licensed under the MIT license. ++package com.mojang.datafixers.util; ++ ++import com.mojang.datafixers.kinds.App; ++import com.mojang.datafixers.kinds.Applicative; ++import com.mojang.datafixers.kinds.CocartesianLike; ++import com.mojang.datafixers.kinds.K1; ++import com.mojang.datafixers.kinds.Traversable; ++ ++import java.util.Objects; ++import java.util.Optional; ++import java.util.function.BiFunction; ++import java.util.function.Consumer; ++import java.util.function.Function; ++ ++public abstract class Either implements App, L> { ++ public static final class Mu implements K1 {} ++ ++ public static Either unbox(final App, L> box) { ++ return (Either) box; ++ } ++ ++ private static final class Left extends Either { ++ private final L value; ++ ++ public Left(final L value) { ++ this.value = value; ++ } ++ ++ @Override ++ public Either mapBoth(final Function f1, final Function f2) { ++ return new Left<>(f1.apply(value)); ++ } ++ ++ @Override ++ public T map(final Function l, final Function r) { ++ return l.apply(value); ++ } ++ ++ @Override ++ public Either ifLeft(Consumer consumer) { ++ consumer.accept(value); ++ return this; ++ } ++ ++ @Override ++ public Either ifRight(Consumer consumer) { ++ return this; ++ } ++ ++ @Override ++ public Optional left() { ++ return Optional.of(value); ++ } ++ ++ @Override ++ public Optional right() { ++ return Optional.empty(); ++ } ++ ++ @Override ++ public String toString() { ++ return "Left[" + value + "]"; ++ } ++ ++ @Override ++ public boolean equals(final Object o) { ++ if (this == o) { ++ return true; ++ } ++ if (o == null || getClass() != o.getClass()) { ++ return false; ++ } ++ final Left left = (Left) o; ++ return Objects.equals(value, left.value); ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hash(value); ++ } ++ } ++ ++ private static final class Right extends Either { ++ private final R value; ++ ++ public Right(final R value) { ++ this.value = value; ++ } ++ ++ @Override ++ public Either mapBoth(final Function f1, final Function f2) { ++ return new Right<>(f2.apply(value)); ++ } ++ ++ @Override ++ public T map(final Function l, final Function r) { ++ return r.apply(value); ++ } ++ ++ @Override ++ public Either ifLeft(Consumer consumer) { ++ return this; ++ } ++ ++ @Override ++ public Either ifRight(Consumer consumer) { ++ consumer.accept(value); ++ return this; ++ } ++ ++ @Override ++ public Optional left() { ++ return Optional.empty(); ++ } ++ ++ @Override ++ public Optional right() { ++ return Optional.of(value); ++ } ++ ++ @Override ++ public String toString() { ++ return "Right[" + value + "]"; ++ } ++ ++ @Override ++ public boolean equals(final Object o) { ++ if (this == o) { ++ return true; ++ } ++ if (o == null || getClass() != o.getClass()) { ++ return false; ++ } ++ final Right right = (Right) o; ++ return Objects.equals(value, right.value); ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hash(value); ++ } ++ } ++ ++ private Either() { ++ } ++ ++ public abstract Either mapBoth(final Function f1, final Function f2); ++ ++ public abstract T map(final Function l, Function r); ++ ++ public abstract Either ifLeft(final Consumer consumer); ++ ++ public abstract Either ifRight(final Consumer consumer); ++ ++ public abstract Optional left(); ++ ++ public abstract Optional right(); ++ ++ public Either mapLeft(final Function l) { ++ return map(t -> left(l.apply(t)), Either::right); ++ } ++ ++ public Either mapRight(final Function l) { ++ return map(Either::left, t -> right(l.apply(t))); ++ } ++ ++ public static Either left(final L value) { ++ return new Left<>(value); ++ } ++ ++ public static Either right(final R value) { ++ return new Right<>(value); ++ } ++ ++ public L orThrow() { ++ return map(l -> l, r -> { ++ if (r instanceof Throwable) { ++ throw new RuntimeException((Throwable) r); ++ } ++ throw new RuntimeException(r.toString()); ++ }); ++ } ++ ++ public Either swap() { ++ return map(Either::right, Either::left); ++ } ++ ++ public Either flatMap(final Function> function) { ++ return map(function, Either::right); ++ } ++ ++ public static final class Instance implements Applicative, Instance.Mu>, Traversable, Instance.Mu>, CocartesianLike, R2, Instance.Mu> { ++ public static final class Mu implements Applicative.Mu, Traversable.Mu, CocartesianLike.Mu {} ++ ++ @Override ++ public App, R> map(final Function func, final App, T> ts) { ++ return Either.unbox(ts).mapLeft(func); ++ } ++ ++ @Override ++ public App, A> point(final A a) { ++ return left(a); ++ } ++ ++ @Override ++ public Function, A>, App, R>> lift1(final App, Function> function) { ++ return a -> Either.unbox(function).flatMap(f -> Either.unbox(a).mapLeft(f)); ++ } ++ ++ @Override ++ public BiFunction, A>, App, B>, App, R>> lift2(final App, BiFunction> function) { ++ return (a, b) -> Either.unbox(function).flatMap( ++ f -> Either.unbox(a).flatMap( ++ av -> Either.unbox(b).mapLeft( ++ bv -> f.apply(av, bv) ++ ) ++ ) ++ ); ++ } ++ ++ @Override ++ public App, B>> traverse(final Applicative applicative, final Function> function, final App, A> input) { ++ return Either.unbox(input).map( ++ l -> { ++ final App b = function.apply(l); ++ return applicative.ap(Either::left, b); ++ }, ++ r -> applicative.point(right(r)) ++ ); ++ } ++ ++ @Override ++ public App, A> to(final App, A> input) { ++ return input; ++ } ++ ++ @Override ++ public App, A> from(final App, A> input) { ++ return input; ++ } ++ } ++} +diff --git a/src/main/java/com/mojang/serialization/Dynamic.java b/src/main/java/com/mojang/serialization/Dynamic.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a75d3db046dc985a03b4b870c91f41de1bd66bad +--- /dev/null ++++ b/src/main/java/com/mojang/serialization/Dynamic.java +@@ -0,0 +1,196 @@ ++// Copyright (c) Microsoft Corporation. All rights reserved. ++// Licensed under the MIT license. ++package com.mojang.serialization; ++ ++import com.google.common.collect.ImmutableMap; ++import com.mojang.datafixers.DataFixUtils; ++import com.mojang.datafixers.util.Pair; ++ ++import javax.annotation.Nullable; ++import java.nio.ByteBuffer; ++import java.util.Map; ++import java.util.Objects; ++import java.util.function.Function; ++import java.util.stream.IntStream; ++import java.util.stream.LongStream; ++import java.util.stream.Stream; ++ ++@SuppressWarnings("unused") ++public class Dynamic extends DynamicLike { ++ private final T value; ++ ++ public Dynamic(final DynamicOps ops) { ++ this(ops, ops.empty()); ++ } ++ ++ public Dynamic(final DynamicOps ops, @Nullable final T value) { ++ super(ops); ++ this.value = value == null ? ops.empty() : value; ++ } ++ ++ public T getValue() { ++ return value; ++ } ++ ++ public Dynamic map(final Function function) { ++ return new Dynamic<>(ops, function.apply(value)); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public Dynamic castTyped(final DynamicOps ops) { ++ if (!Objects.equals(this.ops, ops)) { ++ throw new IllegalStateException("Dynamic type doesn't match"); ++ } ++ return (Dynamic) this; ++ } ++ ++ public U cast(final DynamicOps ops) { ++ return castTyped(ops).getValue(); ++ } ++ ++ public OptionalDynamic merge(final Dynamic value) { ++ final DataResult merged = ops.mergeToList(this.value, value.cast(ops)); ++ return new OptionalDynamic<>(ops, merged.map(m -> new Dynamic<>(ops, m))); ++ } ++ ++ public OptionalDynamic merge(final Dynamic key, final Dynamic value) { ++ final DataResult merged = ops.mergeToMap(this.value, key.cast(ops), value.cast(ops)); ++ return new OptionalDynamic<>(ops, merged.map(m -> new Dynamic<>(ops, m))); ++ } ++ ++ public DataResult, Dynamic>> getMapValues() { ++ return ops.getMapValues(value).map(map -> { ++ final ImmutableMap.Builder, Dynamic> builder = ImmutableMap.builder(); ++ map.forEach(entry -> builder.put(new Dynamic<>(ops, entry.getFirst()), new Dynamic<>(ops, entry.getSecond()))); ++ return builder.build(); ++ }); ++ } ++ ++ public Dynamic updateMapValues(final Function, Dynamic>, Pair, Dynamic>> updater) { ++ return DataFixUtils.orElse(getMapValues().map(map -> map.entrySet().stream().map(e -> { ++ final Pair, Dynamic> pair = updater.apply(Pair.of(e.getKey(), e.getValue())); ++ return Pair.of(pair.getFirst().castTyped(ops), pair.getSecond().castTyped(ops)); ++ }).collect(Pair.toMap())).map(this::createMap).result(), this); ++ } ++ ++ @Override ++ public DataResult asNumber() { ++ return ops.getNumberValue(value); ++ } ++ ++ @Override ++ public DataResult asString() { ++ return ops.getStringValue(value); ++ } ++ ++ @Override ++ public DataResult>> asStreamOpt() { ++ return ops.getStream(value).map(s -> s.map(e -> new Dynamic<>(ops, e))); ++ } ++ ++ @Override ++ public DataResult, Dynamic>>> asMapOpt() { ++ return ops.getMapValues(value).map(s -> s.map(p -> Pair.of(new Dynamic<>(ops, p.getFirst()), new Dynamic<>(ops, p.getSecond())))); ++ } ++ ++ @Override ++ public DataResult asByteBufferOpt() { ++ return ops.getByteBuffer(value); ++ } ++ ++ @Override ++ public DataResult asIntStreamOpt() { ++ return ops.getIntStream(value); ++ } ++ ++ @Override ++ public DataResult asLongStreamOpt() { ++ return ops.getLongStream(value); ++ } ++ ++ @Override ++ public OptionalDynamic get(final String key) { ++ return new OptionalDynamic<>(ops, ops.getMap(value).flatMap(m -> { ++ final T value = m.get(key); ++ if (value == null) { ++ return DataResult.error("key missing: " + key + " in " + this.value); ++ } ++ return DataResult.success(new Dynamic<>(ops, value)); ++ })); ++ } ++ ++ @Override ++ public DataResult getGeneric(final T key) { ++ return ops.getGeneric(value, key); ++ } ++ ++ public Dynamic remove(final String key) { ++ return map(v -> ops.remove(v, key)); ++ } ++ ++ public Dynamic set(final String key, final Dynamic value) { ++ return map(v -> ops.set(v, key, value.cast(ops))); ++ } ++ ++ public Dynamic update(final String key, final Function, Dynamic> function) { ++ return map(v -> ops.update(v, key, value -> function.apply(new Dynamic<>(ops, value)).cast(ops))); ++ } ++ ++ public Dynamic updateGeneric(final T key, final Function function) { ++ return map(v -> ops.updateGeneric(v, key, function)); ++ } ++ ++ @Override ++ public DataResult getElement(final String key) { ++ return getElementGeneric(ops.createString(key)); ++ } ++ ++ @Override ++ public DataResult getElementGeneric(final T key) { ++ return ops.getGeneric(value, key); ++ } ++ ++ @Override ++ public boolean equals(final Object o) { ++ if (this == o) { ++ return true; ++ } ++ if (o == null || getClass() != o.getClass()) { ++ return false; ++ } ++ final Dynamic dynamic = (Dynamic) o; ++ return Objects.equals(ops, dynamic.ops) && Objects.equals(value, dynamic.value); ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hash(ops, value); ++ } ++ ++ @Override ++ public String toString() { ++ return String.format("%s[%s]", ops, value); ++ } ++ ++ public Dynamic convert(final DynamicOps outOps) { ++ return new Dynamic<>(outOps, convert(ops, outOps, value)); ++ } ++ ++ public V into(final Function, ? extends V> action) { ++ return action.apply(this); ++ } ++ ++ @Override ++ public DataResult> decode(final Decoder decoder) { ++ return decoder.decode(ops, value).map(p -> p.mapFirst(Function.identity())); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static T convert(final DynamicOps inOps, final DynamicOps outOps, final S input) { ++ if (Objects.equals(inOps, outOps)) { ++ return (T) input; ++ } ++ ++ return inOps.convertTo(outOps, input); ++ } ++} +diff --git a/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java b/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java +new file mode 100644 +index 0000000000000000000000000000000000000000..de7d1e5e0319c65775d932144c268c2d55bb7dc7 +--- /dev/null ++++ b/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java +@@ -0,0 +1,130 @@ ++package com.mojang.serialization.codecs; ++ ++import com.mojang.datafixers.util.Pair; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.DataResult; ++import com.mojang.serialization.Decoder; ++import com.mojang.serialization.DynamicOps; ++import com.mojang.serialization.Encoder; ++import com.mojang.serialization.MapCodec; ++import com.mojang.serialization.MapLike; ++import com.mojang.serialization.RecordBuilder; ++ ++import java.util.function.Function; ++import java.util.stream.Stream; ++ ++public class KeyDispatchCodec extends MapCodec { ++ private final String typeKey; ++ private final Codec keyCodec; ++ private final String valueKey = "value"; ++ private final Function> type; ++ private final Function>> decoder; ++ private final Function>> encoder; ++ ++ private final boolean assumeMap; ++ ++ /** ++ * Will assume that the result of all elements is a map ++ */ ++ public static KeyDispatchCodec unsafe(final String typeKey, final Codec keyCodec, final Function> type, final Function>> decoder, final Function>> encoder) { ++ return new KeyDispatchCodec<>(typeKey, keyCodec, type, decoder, encoder, true); ++ } ++ ++ protected KeyDispatchCodec(final String typeKey, final Codec keyCodec, final Function> type, final Function>> decoder, final Function>> encoder, final boolean assumeMap) { ++ this.typeKey = typeKey; ++ this.keyCodec = keyCodec; ++ this.type = type; ++ this.decoder = decoder; ++ this.encoder = encoder; ++ this.assumeMap = assumeMap; ++ } ++ ++ /** ++ * Assumes codec(type(V)) is Codec ++ */ ++ public KeyDispatchCodec(final String typeKey, final Codec keyCodec, final Function> type, final Function>> codec) { ++ this(typeKey, keyCodec, type, codec, v -> getCodec(type, codec, v), false); ++ } ++ ++ @Override ++ public DataResult decode(final DynamicOps ops, final MapLike input) { ++ final T elementName = input.get(typeKey); ++ if (elementName == null) { ++ return DataResult.error("Input does not contain a key [" + typeKey + "]: " + input); ++ } ++ ++ return keyCodec.decode(ops, elementName).flatMap(type -> { ++ final DataResult> elementDecoder = decoder.apply(type.getFirst()); ++ return elementDecoder.flatMap(c -> { ++ if (ops.compressMaps()) { ++ final T value = input.get(ops.createString(valueKey)); ++ if (value == null) { ++ return DataResult.error("Input does not have a \"value\" entry: " + input); ++ } ++ return c.parse(ops, value).map(Function.identity()); ++ } ++ if (c instanceof MapCodecCodec) { ++ return ((MapCodecCodec) c).codec().decode(ops, input).map(Function.identity()); ++ } ++ if (assumeMap) { ++ return c.decode(ops, ops.createMap(input.entries())).map(Pair::getFirst); ++ } ++ return c.decode(ops, input.get(valueKey)).map(Pair::getFirst); ++ }); ++ }); ++ } ++ ++ @Override ++ public RecordBuilder encode(final V input, final DynamicOps ops, final RecordBuilder prefix) { ++ final DataResult> elementEncoder = encoder.apply(input); ++ final RecordBuilder builder = prefix.withErrorsFrom(elementEncoder); ++ if (!elementEncoder.result().isPresent()) { ++ return builder; ++ } ++ ++ final Encoder c = elementEncoder.result().get(); ++ if (ops.compressMaps()) { ++ return prefix ++ .add(typeKey, type.apply(input).flatMap(t -> keyCodec.encodeStart(ops, t))) ++ .add(valueKey, c.encodeStart(ops, input)); ++ } ++ if (c instanceof MapCodecCodec) { ++ return ((MapCodecCodec) c).codec().encode(input, ops, prefix) ++ .add(typeKey, type.apply(input).flatMap(t -> keyCodec.encodeStart(ops, t))); ++ } ++ ++ final T typeString = ops.createString(typeKey); ++ ++ final DataResult result = c.encodeStart(ops, input); ++ if (assumeMap) { ++ final DataResult> element = result.flatMap(ops::getMap); ++ return element.map(map -> { ++ prefix.add(typeString, type.apply(input).flatMap(t -> keyCodec.encodeStart(ops, t))); ++ map.entries().forEach(pair -> { ++ if (!pair.getFirst().equals(typeString)) { ++ prefix.add(pair.getFirst(), pair.getSecond()); ++ } ++ }); ++ return prefix; ++ }).result().orElseGet(() -> prefix.withErrorsFrom(element)); ++ } ++ prefix.add(typeString, type.apply(input).flatMap(t -> keyCodec.encodeStart(ops, t))); ++ prefix.add(valueKey, result); ++ return prefix; ++ } ++ ++ @Override ++ public Stream keys(final DynamicOps ops) { ++ return Stream.of(typeKey, valueKey).map(ops::createString); ++ } ++ ++ @SuppressWarnings("unchecked") ++ private static DataResult> getCodec(final Function> type, final Function>> encoder, final V input) { ++ return type.apply(input).>flatMap(k -> encoder.apply(k).map(Function.identity())).map(c -> ((Encoder) c)); ++ } ++ ++ @Override ++ public String toString() { ++ return "KeyDispatchCodec[" + keyCodec.toString() + " " + type + " " + decoder + "]"; ++ } ++} +diff --git a/src/main/java/net/minecraft/EnumChatFormat.java b/src/main/java/net/minecraft/EnumChatFormat.java +new file mode 100644 +index 0000000000000000000000000000000000000000..75e38f05c10713f773a8763100dfc0777521dba6 +--- /dev/null ++++ b/src/main/java/net/minecraft/EnumChatFormat.java +@@ -0,0 +1,122 @@ ++package net.minecraft; ++ ++import com.google.common.collect.Lists; ++import java.util.Arrays; ++import java.util.Collection; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.regex.Pattern; ++import java.util.stream.Collectors; ++import javax.annotation.Nullable; ++ ++public enum EnumChatFormat { ++ ++ BLACK("BLACK", '0', 0, 0), DARK_BLUE("DARK_BLUE", '1', 1, 170), DARK_GREEN("DARK_GREEN", '2', 2, 43520), DARK_AQUA("DARK_AQUA", '3', 3, 43690), DARK_RED("DARK_RED", '4', 4, 11141120), DARK_PURPLE("DARK_PURPLE", '5', 5, 11141290), GOLD("GOLD", '6', 6, 16755200), GRAY("GRAY", '7', 7, 11184810), DARK_GRAY("DARK_GRAY", '8', 8, 5592405), BLUE("BLUE", '9', 9, 5592575), GREEN("GREEN", 'a', 10, 5635925), AQUA("AQUA", 'b', 11, 5636095), RED("RED", 'c', 12, 16733525), LIGHT_PURPLE("LIGHT_PURPLE", 'd', 13, 16733695), YELLOW("YELLOW", 'e', 14, 16777045), WHITE("WHITE", 'f', 15, 16777215), OBFUSCATED("OBFUSCATED", 'k', true), BOLD("BOLD", 'l', true), STRIKETHROUGH("STRIKETHROUGH", 'm', true), UNDERLINE("UNDERLINE", 'n', true), ITALIC("ITALIC", 'o', true), RESET("RESET", 'r', -1, (Integer) null); ++ ++ private static final Map w = (Map) Arrays.stream(values()).collect(Collectors.toMap((enumchatformat) -> { ++ return c(enumchatformat.y); ++ }, (enumchatformat) -> { ++ return enumchatformat; ++ })); ++ private static final Pattern x = Pattern.compile("(?i)\u00a7[0-9A-FK-OR]"); ++ private final String y; ++ public final char character; ++ private final boolean A; ++ private final String B; ++ private final int C; ++ @Nullable ++ private final Integer D; ++ ++ private static String c(String s) { ++ return s.toLowerCase(Locale.ROOT).replaceAll("[^a-z]", ""); ++ } ++ ++ private EnumChatFormat(String s, char c0, int i, Integer integer) { ++ this(s, c0, false, i, integer); ++ } ++ ++ private EnumChatFormat(String s, char c0, boolean flag) { ++ this(s, c0, flag, -1, (Integer) null); ++ } ++ ++ private EnumChatFormat(String s, char c0, boolean flag, int i, Integer integer) { ++ this.y = s; ++ this.character = c0; ++ this.A = flag; ++ this.C = i; ++ this.D = integer; ++ this.B = "\u00a7" + c0; ++ } ++ ++ public int b() { ++ return this.C; ++ } ++ ++ public boolean isFormat() { ++ return this.A; ++ } ++ ++ public boolean d() { ++ return !this.A && this != EnumChatFormat.RESET; ++ } ++ ++ @Nullable ++ public Integer e() { ++ return this.D; ++ } ++ ++ public String f() { ++ return this.name().toLowerCase(Locale.ROOT); ++ } ++ ++ public String toString() { ++ return this.B; ++ } ++ ++ @Nullable ++ public static String a(@Nullable String s) { ++ return s == null ? null : EnumChatFormat.x.matcher(s).replaceAll(""); ++ } ++ ++ @Nullable ++ public static EnumChatFormat b(@Nullable String s) { ++ return s == null ? null : (EnumChatFormat) EnumChatFormat.w.get(c(s)); ++ } ++ ++ @Nullable ++ public static EnumChatFormat a(int i) { ++ if (i < 0) { ++ return EnumChatFormat.RESET; ++ } else { ++ EnumChatFormat[] aenumchatformat = values(); ++ int j = aenumchatformat.length; ++ ++ for (int k = 0; k < j; ++k) { ++ EnumChatFormat enumchatformat = aenumchatformat[k]; ++ ++ if (enumchatformat.b() == i) { ++ return enumchatformat; ++ } ++ } ++ ++ return null; ++ } ++ } ++ ++ public static Collection a(boolean flag, boolean flag1) { ++ List list = Lists.newArrayList(); ++ EnumChatFormat[] aenumchatformat = values(); ++ int i = aenumchatformat.length; ++ ++ for (int j = 0; j < i; ++j) { ++ EnumChatFormat enumchatformat = aenumchatformat[j]; ++ ++ if ((!enumchatformat.d() || flag) && (!enumchatformat.isFormat() || flag1)) { ++ list.add(enumchatformat.f()); ++ } ++ } ++ ++ return list; ++ } ++} +diff --git a/src/main/java/net/minecraft/SharedConstants.java b/src/main/java/net/minecraft/SharedConstants.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b92975aa85fd79c70d6bec014284e8f55d0d3a4b +--- /dev/null ++++ b/src/main/java/net/minecraft/SharedConstants.java +@@ -0,0 +1,56 @@ ++package net.minecraft; ++ ++import com.mojang.bridge.game.GameVersion; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import io.netty.util.ResourceLeakDetector; ++import io.netty.util.ResourceLeakDetector.Level; ++import java.time.Duration; ++import net.minecraft.commands.CommandExceptionProvider; ++ ++public class SharedConstants { ++ ++ public static final Level a = Level.DISABLED; ++ public static final long b = Duration.ofMillis(300L).toNanos(); ++ public static boolean c = true; ++ public static boolean d; ++ public static final char[] allowedCharacters = new char[]{'/', '\n', '\r', '\t', '\u0000', '\f', '`', '?', '*', '\\', '<', '>', '|', '"', ':'}; ++ private static GameVersion f; ++ ++ public static boolean isAllowedChatCharacter(char c0) { ++ return c0 != 167 && c0 >= ' ' && c0 != 127; ++ } ++ ++ public static String a(String s) { ++ StringBuilder stringbuilder = new StringBuilder(); ++ char[] achar = s.toCharArray(); ++ int i = achar.length; ++ ++ for (int j = 0; j < i; ++j) { ++ char c0 = achar[j]; ++ ++ if (isAllowedChatCharacter(c0)) { ++ stringbuilder.append(c0); ++ } ++ } ++ ++ return stringbuilder.toString(); ++ } ++ ++ public static GameVersion getGameVersion() { ++ if (SharedConstants.f == null) { ++ SharedConstants.f = MinecraftVersion.a(); ++ } ++ ++ return SharedConstants.f; ++ } ++ ++ public static int b() { ++ return 754; ++ } ++ ++ static { ++ ResourceLeakDetector.setLevel(SharedConstants.a); ++ CommandSyntaxException.ENABLE_COMMAND_STACK_TRACES = false; ++ CommandSyntaxException.BUILT_IN_EXCEPTIONS = new CommandExceptionProvider(); ++ } ++} +diff --git a/src/main/java/net/minecraft/SystemUtils.java b/src/main/java/net/minecraft/SystemUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..14b1a51b9b675aa175c32990402551fa43ec1599 +--- /dev/null ++++ b/src/main/java/net/minecraft/SystemUtils.java +@@ -0,0 +1,504 @@ ++package net.minecraft; ++ ++import com.google.common.collect.Iterators; ++import com.google.common.collect.Lists; ++import com.google.common.util.concurrent.MoreExecutors; ++import com.mojang.datafixers.DSL.TypeReference; ++import com.mojang.datafixers.DataFixUtils; ++import com.mojang.datafixers.types.Type; ++import com.mojang.serialization.DataResult; ++import it.unimi.dsi.fastutil.Hash.Strategy; ++import java.io.File; ++import java.io.IOException; ++import java.lang.management.ManagementFactory; ++import java.lang.management.RuntimeMXBean; ++import java.nio.file.Files; ++import java.nio.file.LinkOption; ++import java.nio.file.Path; ++import java.time.Instant; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Map.Entry; ++import java.util.Optional; ++import java.util.Random; ++import java.util.UUID; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.CompletionException; ++import java.util.concurrent.Executor; ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; ++import java.util.concurrent.ForkJoinPool; ++import java.util.concurrent.ForkJoinWorkerThread; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.BooleanSupplier; ++import java.util.function.Consumer; ++import java.util.function.LongSupplier; ++import java.util.function.Supplier; ++import java.util.stream.Collector; ++import java.util.stream.Collectors; ++import java.util.stream.IntStream; ++import java.util.stream.Stream; ++import javax.annotation.Nullable; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.server.DispenserRegistry; ++import net.minecraft.util.MathHelper; ++import net.minecraft.util.datafix.DataConverterRegistry; ++import net.minecraft.world.level.block.state.properties.IBlockState; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class SystemUtils { ++ ++ private static final AtomicInteger c = new AtomicInteger(1); ++ private static final ExecutorService d = a("Bootstrap"); ++ private static final ExecutorService e = a("Main"); ++ private static final ExecutorService f = n(); ++ public static LongSupplier a = System::nanoTime; ++ public static final UUID b = new UUID(0L, 0L); ++ private static final Logger LOGGER = LogManager.getLogger(); ++ ++ public static Collector, ?, Map> a() { ++ return Collectors.toMap(Entry::getKey, Entry::getValue); ++ } ++ ++ public static > String a(IBlockState iblockstate, Object object) { ++ return iblockstate.a((Comparable) object); ++ } ++ ++ public static String a(String s, @Nullable MinecraftKey minecraftkey) { ++ return minecraftkey == null ? s + ".unregistered_sadface" : s + '.' + minecraftkey.getNamespace() + '.' + minecraftkey.getKey().replace('/', '.'); ++ } ++ ++ public static long getMonotonicMillis() { ++ return getMonotonicNanos() / 1000000L; ++ } ++ ++ public static long getMonotonicNanos() { ++ return SystemUtils.a.getAsLong(); ++ } ++ ++ public static long getTimeMillis() { ++ return Instant.now().toEpochMilli(); ++ } ++ ++ private static ExecutorService a(String s) { ++ int i = MathHelper.clamp(Runtime.getRuntime().availableProcessors() - 1, 1, 7); ++ Object object; ++ ++ if (i <= 0) { ++ object = MoreExecutors.newDirectExecutorService(); ++ } else { ++ object = new ForkJoinPool(i, (forkjoinpool) -> { ++ ForkJoinWorkerThread forkjoinworkerthread = new ForkJoinWorkerThread(forkjoinpool) { ++ protected void onTermination(Throwable throwable) { ++ if (throwable != null) { ++ SystemUtils.LOGGER.warn("{} died", this.getName(), throwable); ++ } else { ++ SystemUtils.LOGGER.debug("{} shutdown", this.getName()); ++ } ++ ++ super.onTermination(throwable); ++ } ++ }; ++ ++ forkjoinworkerthread.setName("Worker-" + s + "-" + SystemUtils.c.getAndIncrement()); ++ return forkjoinworkerthread; ++ }, SystemUtils::a, true); ++ } ++ ++ return (ExecutorService) object; ++ } ++ ++ public static Executor e() { ++ return SystemUtils.d; ++ } ++ ++ public static Executor f() { ++ return SystemUtils.e; ++ } ++ ++ public static Executor g() { ++ return SystemUtils.f; ++ } ++ ++ public static void h() { ++ a(SystemUtils.e); ++ a(SystemUtils.f); ++ } ++ ++ private static void a(ExecutorService executorservice) { ++ executorservice.shutdown(); ++ ++ boolean flag; ++ ++ try { ++ flag = executorservice.awaitTermination(3L, TimeUnit.SECONDS); ++ } catch (InterruptedException interruptedexception) { ++ flag = false; ++ } ++ ++ if (!flag) { ++ executorservice.shutdownNow(); ++ } ++ ++ } ++ ++ private static ExecutorService n() { ++ return Executors.newCachedThreadPool((runnable) -> { ++ Thread thread = new Thread(runnable); ++ ++ thread.setName("IO-Worker-" + SystemUtils.c.getAndIncrement()); ++ thread.setUncaughtExceptionHandler(SystemUtils::a); ++ return thread; ++ }); ++ } ++ ++ private static void a(Thread thread, Throwable throwable) { ++ c(throwable); ++ if (throwable instanceof CompletionException) { ++ throwable = throwable.getCause(); ++ } ++ ++ if (throwable instanceof ReportedException) { ++ DispenserRegistry.a(((ReportedException) throwable).a().e()); ++ System.exit(-1); ++ } ++ ++ SystemUtils.LOGGER.error(String.format("Caught exception in thread %s", thread), throwable); ++ } ++ ++ @Nullable ++ public static Type a(TypeReference typereference, String s) { ++ return !SharedConstants.c ? null : b(typereference, s); ++ } ++ ++ @Nullable ++ private static Type b(TypeReference typereference, String s) { ++ Type type = null; ++ ++ try { ++ type = DataConverterRegistry.a().getSchema(DataFixUtils.makeKey(SharedConstants.getGameVersion().getWorldVersion())).getChoiceType(typereference, s); ++ } catch (IllegalArgumentException illegalargumentexception) { ++ SystemUtils.LOGGER.error("No data fixer registered for {}", s); ++ if (SharedConstants.d) { ++ throw illegalargumentexception; ++ } ++ } ++ ++ return type; ++ } ++ ++ public static SystemUtils.OS i() { ++ String s = System.getProperty("os.name").toLowerCase(Locale.ROOT); ++ ++ return s.contains("win") ? SystemUtils.OS.WINDOWS : (s.contains("mac") ? SystemUtils.OS.OSX : (s.contains("solaris") ? SystemUtils.OS.SOLARIS : (s.contains("sunos") ? SystemUtils.OS.SOLARIS : (s.contains("linux") ? SystemUtils.OS.LINUX : (s.contains("unix") ? SystemUtils.OS.LINUX : SystemUtils.OS.UNKNOWN))))); ++ } ++ ++ public static Stream j() { ++ RuntimeMXBean runtimemxbean = ManagementFactory.getRuntimeMXBean(); ++ ++ return runtimemxbean.getInputArguments().stream().filter((s) -> { ++ return s.startsWith("-X"); ++ }); ++ } ++ ++ public static T a(List list) { ++ return list.get(list.size() - 1); ++ } ++ ++ public static T a(Iterable iterable, @Nullable T t0) { ++ Iterator iterator = iterable.iterator(); ++ T t1 = iterator.next(); ++ ++ if (t0 != null) { ++ Object object = t1; ++ ++ while (object != t0) { ++ if (iterator.hasNext()) { ++ object = iterator.next(); ++ } ++ } ++ ++ if (iterator.hasNext()) { ++ return iterator.next(); ++ } ++ } ++ ++ return t1; ++ } ++ ++ public static T b(Iterable iterable, @Nullable T t0) { ++ Iterator iterator = iterable.iterator(); ++ ++ Object object; ++ Object object1; ++ ++ for (object1 = null; iterator.hasNext(); object1 = object) { ++ object = iterator.next(); ++ if (object == t0) { ++ if (object1 == null) { ++ object1 = iterator.hasNext() ? Iterators.getLast(iterator) : t0; ++ } ++ break; ++ } ++ } ++ ++ return object1; ++ } ++ ++ public static T a(Supplier supplier) { ++ return supplier.get(); ++ } ++ ++ public static T a(T t0, Consumer consumer) { ++ consumer.accept(t0); ++ return t0; ++ } ++ ++ public static Strategy k() { ++ return SystemUtils.IdentityHashingStrategy.INSTANCE; ++ } ++ ++ public static CompletableFuture> b(List> list) { ++ List list1 = Lists.newArrayListWithCapacity(list.size()); ++ CompletableFuture[] acompletablefuture = new CompletableFuture[list.size()]; ++ CompletableFuture completablefuture = new CompletableFuture(); ++ ++ list.forEach((completablefuture1) -> { ++ int i = list1.size(); ++ ++ list1.add((Object) null); ++ acompletablefuture[i] = completablefuture1.whenComplete((object, throwable) -> { ++ if (throwable != null) { ++ completablefuture.completeExceptionally(throwable); ++ } else { ++ list1.set(i, object); ++ } ++ ++ }); ++ }); ++ return CompletableFuture.allOf(acompletablefuture).applyToEither(completablefuture, (ovoid) -> { ++ return list1; ++ }); ++ } ++ ++ public static Stream a(Optional optional) { ++ return (Stream) DataFixUtils.orElseGet(optional.map(Stream::of), Stream::empty); ++ } ++ ++ public static Optional a(Optional optional, Consumer consumer, Runnable runnable) { ++ if (optional.isPresent()) { ++ consumer.accept(optional.get()); ++ } else { ++ runnable.run(); ++ } ++ ++ return optional; ++ } ++ ++ public static Runnable a(Runnable runnable, Supplier supplier) { ++ return runnable; ++ } ++ ++ public static T c(T t0) { ++ if (SharedConstants.d) { ++ SystemUtils.LOGGER.error("Trying to throw a fatal exception, pausing in IDE", t0); ++ ++ while (true) { ++ try { ++ Thread.sleep(1000L); ++ SystemUtils.LOGGER.error("paused"); ++ } catch (InterruptedException interruptedexception) { ++ return t0; ++ } ++ } ++ } else { ++ return t0; ++ } ++ } ++ ++ public static String d(Throwable throwable) { ++ return throwable.getCause() != null ? d(throwable.getCause()) : (throwable.getMessage() != null ? throwable.getMessage() : throwable.toString()); ++ } ++ ++ public static T a(T[] at, Random random) { ++ return at[random.nextInt(at.length)]; ++ } ++ ++ public static int a(int[] aint, Random random) { ++ return aint[random.nextInt(aint.length)]; ++ } ++ ++ private static BooleanSupplier a(final Path path, final Path path1) { ++ return new BooleanSupplier() { ++ public boolean getAsBoolean() { ++ try { ++ Files.move(path, path1); ++ return true; ++ } catch (IOException ioexception) { ++ SystemUtils.LOGGER.error("Failed to rename", ioexception); ++ return false; ++ } ++ } ++ ++ public String toString() { ++ return "rename " + path + " to " + path1; ++ } ++ }; ++ } ++ ++ private static BooleanSupplier a(final Path path) { ++ return new BooleanSupplier() { ++ public boolean getAsBoolean() { ++ try { ++ Files.deleteIfExists(path); ++ return true; ++ } catch (IOException ioexception) { ++ SystemUtils.LOGGER.warn("Failed to delete", ioexception); ++ return false; ++ } ++ } ++ ++ public String toString() { ++ return "delete old " + path; ++ } ++ }; ++ } ++ ++ private static BooleanSupplier b(final Path path) { ++ return new BooleanSupplier() { ++ public boolean getAsBoolean() { ++ return !Files.exists(path, new LinkOption[0]); ++ } ++ ++ public String toString() { ++ return "verify that " + path + " is deleted"; ++ } ++ }; ++ } ++ ++ private static BooleanSupplier c(final Path path) { ++ return new BooleanSupplier() { ++ public boolean getAsBoolean() { ++ return Files.isRegularFile(path, new LinkOption[0]); ++ } ++ ++ public String toString() { ++ return "verify that " + path + " is present"; ++ } ++ }; ++ } ++ ++ private static boolean a(BooleanSupplier... abooleansupplier) { ++ BooleanSupplier[] abooleansupplier1 = abooleansupplier; ++ int i = abooleansupplier.length; ++ ++ for (int j = 0; j < i; ++j) { ++ BooleanSupplier booleansupplier = abooleansupplier1[j]; ++ ++ if (!booleansupplier.getAsBoolean()) { ++ SystemUtils.LOGGER.warn("Failed to execute {}", booleansupplier); ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ private static boolean a(int i, String s, BooleanSupplier... abooleansupplier) { ++ for (int j = 0; j < i; ++j) { ++ if (a(abooleansupplier)) { ++ return true; ++ } ++ ++ SystemUtils.LOGGER.error("Failed to {}, retrying {}/{}", s, j, i); ++ } ++ ++ SystemUtils.LOGGER.error("Failed to {}, aborting, progress might be lost", s); ++ return false; ++ } ++ ++ public static void a(File file, File file1, File file2) { ++ a(file.toPath(), file1.toPath(), file2.toPath()); ++ } ++ ++ public static void a(Path path, Path path1, Path path2) { ++ boolean flag = true; ++ ++ if (!Files.exists(path, new LinkOption[0]) || a(10, "create backup " + path2, a(path2), a(path, path2), c(path2))) { ++ if (a(10, "remove old " + path, a(path), b(path))) { ++ if (!a(10, "replace " + path + " with " + path1, a(path1, path), c(path))) { ++ a(10, "restore " + path + " from " + path2, a(path2, path), c(path)); ++ } ++ ++ } ++ } ++ } ++ ++ public static Consumer a(String s, Consumer consumer) { ++ return (s1) -> { ++ consumer.accept(s + s1); ++ }; ++ } ++ ++ public static DataResult a(IntStream intstream, int i) { ++ int[] aint = intstream.limit((long) (i + 1)).toArray(); ++ ++ if (aint.length != i) { ++ String s = "Input is not a list of " + i + " ints"; ++ ++ return aint.length >= i ? DataResult.error(s, Arrays.copyOf(aint, i)) : DataResult.error(s); ++ } else { ++ return DataResult.success(aint); ++ } ++ } ++ ++ public static void l() { ++ Thread thread = new Thread("Timer hack thread") { ++ public void run() { ++ while (true) { ++ try { ++ Thread.sleep(2147483647L); ++ } catch (InterruptedException interruptedexception) { ++ SystemUtils.LOGGER.warn("Timer hack thread interrupted, that really should not happen"); ++ return; ++ } ++ } ++ } ++ }; ++ ++ thread.setDaemon(true); ++ thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(SystemUtils.LOGGER)); ++ thread.start(); ++ } ++ ++ static enum IdentityHashingStrategy implements Strategy { ++ ++ INSTANCE; ++ ++ private IdentityHashingStrategy() {} ++ ++ public int hashCode(Object object) { ++ return System.identityHashCode(object); ++ } ++ ++ public boolean equals(Object object, Object object1) { ++ return object == object1; ++ } ++ } ++ ++ public static enum OS { ++ ++ LINUX, SOLARIS, WINDOWS { ++ }, ++ OSX { ++ }, ++ UNKNOWN; ++ ++ private OS() {} ++ } ++} +diff --git a/src/main/java/net/minecraft/advancements/critereon/CriterionTriggerAbstract.java b/src/main/java/net/minecraft/advancements/critereon/CriterionTriggerAbstract.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c2ee816af000f0c94782d704e6372cd93fd34bb3 +--- /dev/null ++++ b/src/main/java/net/minecraft/advancements/critereon/CriterionTriggerAbstract.java +@@ -0,0 +1,92 @@ ++package net.minecraft.advancements.critereon; ++ ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import com.google.common.collect.Sets; ++import com.google.gson.JsonObject; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.function.Predicate; ++import net.minecraft.advancements.CriterionTrigger; ++import net.minecraft.server.AdvancementDataPlayer; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.world.level.storage.loot.LootTableInfo; ++ ++public abstract class CriterionTriggerAbstract implements CriterionTrigger { ++ ++ private final Map>> a = Maps.newIdentityHashMap(); ++ ++ public CriterionTriggerAbstract() {} ++ ++ @Override ++ public final void a(AdvancementDataPlayer advancementdataplayer, CriterionTrigger.a criteriontrigger_a) { ++ ((Set) this.a.computeIfAbsent(advancementdataplayer, (advancementdataplayer1) -> { ++ return Sets.newHashSet(); ++ })).add(criteriontrigger_a); ++ } ++ ++ @Override ++ public final void b(AdvancementDataPlayer advancementdataplayer, CriterionTrigger.a criteriontrigger_a) { ++ Set> set = (Set) this.a.get(advancementdataplayer); ++ ++ if (set != null) { ++ set.remove(criteriontrigger_a); ++ if (set.isEmpty()) { ++ this.a.remove(advancementdataplayer); ++ } ++ } ++ ++ } ++ ++ @Override ++ public final void a(AdvancementDataPlayer advancementdataplayer) { ++ this.a.remove(advancementdataplayer); ++ } ++ ++ protected abstract T b(JsonObject jsonobject, CriterionConditionEntity.b criterionconditionentity_b, LootDeserializationContext lootdeserializationcontext); ++ ++ @Override ++ public final T a(JsonObject jsonobject, LootDeserializationContext lootdeserializationcontext) { ++ CriterionConditionEntity.b criterionconditionentity_b = CriterionConditionEntity.b.a(jsonobject, "player", lootdeserializationcontext); ++ ++ return this.b(jsonobject, criterionconditionentity_b, lootdeserializationcontext); ++ } ++ ++ protected void a(EntityPlayer entityplayer, Predicate predicate) { ++ AdvancementDataPlayer advancementdataplayer = entityplayer.getAdvancementData(); ++ Set> set = (Set) this.a.get(advancementdataplayer); ++ ++ if (set != null && !set.isEmpty()) { ++ LootTableInfo loottableinfo = CriterionConditionEntity.b(entityplayer, entityplayer); ++ List> list = null; ++ Iterator iterator = set.iterator(); ++ ++ CriterionTrigger.a criteriontrigger_a; ++ ++ while (iterator.hasNext()) { ++ criteriontrigger_a = (CriterionTrigger.a) iterator.next(); ++ T t0 = (CriterionInstanceAbstract) criteriontrigger_a.a(); ++ ++ if (t0.b().a(loottableinfo) && predicate.test(t0)) { ++ if (list == null) { ++ list = Lists.newArrayList(); ++ } ++ ++ list.add(criteriontrigger_a); ++ } ++ } ++ ++ if (list != null) { ++ iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ criteriontrigger_a = (CriterionTrigger.a) iterator.next(); ++ criteriontrigger_a.a(advancementdataplayer); ++ } ++ } ++ ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/commands/CustomFunction.java b/src/main/java/net/minecraft/commands/CustomFunction.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f96b132bb51c2d97703964a70fcb058f0649ac13 +--- /dev/null ++++ b/src/main/java/net/minecraft/commands/CustomFunction.java +@@ -0,0 +1,156 @@ ++package net.minecraft.commands; ++ ++import com.google.common.collect.Lists; ++import com.mojang.brigadier.ParseResults; ++import com.mojang.brigadier.StringReader; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import java.util.ArrayDeque; ++import java.util.List; ++import java.util.Optional; ++import javax.annotation.Nullable; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.server.CustomFunctionData; ++ ++public class CustomFunction { ++ ++ private final CustomFunction.c[] a; ++ private final MinecraftKey b; ++ ++ public CustomFunction(MinecraftKey minecraftkey, CustomFunction.c[] acustomfunction_c) { ++ this.b = minecraftkey; ++ this.a = acustomfunction_c; ++ } ++ ++ public MinecraftKey a() { ++ return this.b; ++ } ++ ++ public CustomFunction.c[] b() { ++ return this.a; ++ } ++ ++ public static CustomFunction a(MinecraftKey minecraftkey, com.mojang.brigadier.CommandDispatcher com_mojang_brigadier_commanddispatcher, CommandListenerWrapper commandlistenerwrapper, List list) { ++ List list1 = Lists.newArrayListWithCapacity(list.size()); ++ ++ for (int i = 0; i < list.size(); ++i) { ++ int j = i + 1; ++ String s = ((String) list.get(i)).trim(); ++ StringReader stringreader = new StringReader(s); ++ ++ if (stringreader.canRead() && stringreader.peek() != '#') { ++ if (stringreader.peek() == '/') { ++ stringreader.skip(); ++ if (stringreader.peek() == '/') { ++ throw new IllegalArgumentException("Unknown or invalid command '" + s + "' on line " + j + " (if you intended to make a comment, use '#' not '//')"); ++ } ++ ++ String s1 = stringreader.readUnquotedString(); ++ ++ throw new IllegalArgumentException("Unknown or invalid command '" + s + "' on line " + j + " (did you mean '" + s1 + "'? Do not use a preceding forwards slash.)"); ++ } ++ ++ try { ++ ParseResults parseresults = com_mojang_brigadier_commanddispatcher.parse(stringreader, commandlistenerwrapper); ++ ++ if (parseresults.getReader().canRead()) { ++ throw CommandDispatcher.a(parseresults); ++ } ++ ++ list1.add(new CustomFunction.b(parseresults)); ++ } catch (CommandSyntaxException commandsyntaxexception) { ++ throw new IllegalArgumentException("Whilst parsing command on line " + j + ": " + commandsyntaxexception.getMessage()); ++ } ++ } ++ } ++ ++ return new CustomFunction(minecraftkey, (CustomFunction.c[]) list1.toArray(new CustomFunction.c[0])); ++ } ++ ++ public static class a { ++ ++ public static final CustomFunction.a a = new CustomFunction.a((MinecraftKey) null); ++ @Nullable ++ private final MinecraftKey b; ++ private boolean c; ++ private Optional d = Optional.empty(); ++ ++ public a(@Nullable MinecraftKey minecraftkey) { ++ this.b = minecraftkey; ++ } ++ ++ public a(CustomFunction customfunction) { ++ this.c = true; ++ this.b = null; ++ this.d = Optional.of(customfunction); ++ } ++ ++ public Optional a(CustomFunctionData customfunctiondata) { ++ if (!this.c) { ++ if (this.b != null) { ++ this.d = customfunctiondata.a(this.b); ++ } ++ ++ this.c = true; ++ } ++ ++ return this.d; ++ } ++ ++ @Nullable ++ public MinecraftKey a() { ++ return (MinecraftKey) this.d.map((customfunction) -> { ++ return customfunction.b; ++ }).orElse(this.b); ++ } ++ } ++ ++ public static class d implements CustomFunction.c { ++ ++ private final CustomFunction.a a; ++ ++ public d(CustomFunction customfunction) { ++ this.a = new CustomFunction.a(customfunction); ++ } ++ ++ @Override ++ public void a(CustomFunctionData customfunctiondata, CommandListenerWrapper commandlistenerwrapper, ArrayDeque arraydeque, int i) { ++ this.a.a(customfunctiondata).ifPresent((customfunction) -> { ++ CustomFunction.c[] acustomfunction_c = customfunction.b(); ++ int j = i - arraydeque.size(); ++ int k = Math.min(acustomfunction_c.length, j); ++ ++ for (int l = k - 1; l >= 0; --l) { ++ arraydeque.addFirst(new CustomFunctionData.a(customfunctiondata, commandlistenerwrapper, acustomfunction_c[l])); ++ } ++ ++ }); ++ } ++ ++ public String toString() { ++ return "function " + this.a.a(); ++ } ++ } ++ ++ public static class b implements CustomFunction.c { ++ ++ private final ParseResults a; ++ ++ public b(ParseResults parseresults) { ++ this.a = parseresults; ++ } ++ ++ @Override ++ public void a(CustomFunctionData customfunctiondata, CommandListenerWrapper commandlistenerwrapper, ArrayDeque arraydeque, int i) throws CommandSyntaxException { ++ customfunctiondata.getCommandDispatcher().execute(new ParseResults(this.a.getContext().withSource(commandlistenerwrapper), this.a.getReader(), this.a.getExceptions())); ++ } ++ ++ public String toString() { ++ return this.a.getReader().getString(); ++ } ++ } ++ ++ public interface c { ++ ++ void a(CustomFunctionData customfunctiondata, CommandListenerWrapper commandlistenerwrapper, ArrayDeque arraydeque, int i) throws CommandSyntaxException; ++ } ++} +diff --git a/src/main/java/net/minecraft/core/BaseBlockPosition.java b/src/main/java/net/minecraft/core/BaseBlockPosition.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fe3a3ce150de0e689c452b67d480b9d69471b330 +--- /dev/null ++++ b/src/main/java/net/minecraft/core/BaseBlockPosition.java +@@ -0,0 +1,143 @@ ++package net.minecraft.core; ++ ++import com.google.common.base.MoreObjects; ++import com.mojang.serialization.Codec; ++import java.util.stream.IntStream; ++import javax.annotation.concurrent.Immutable; ++import net.minecraft.SystemUtils; ++import net.minecraft.util.MathHelper; ++ ++@Immutable ++public class BaseBlockPosition implements Comparable { ++ ++ public static final Codec c = Codec.INT_STREAM.comapFlatMap((intstream) -> { ++ return SystemUtils.a(intstream, 3).map((aint) -> { ++ return new BaseBlockPosition(aint[0], aint[1], aint[2]); ++ }); ++ }, (baseblockposition) -> { ++ return IntStream.of(new int[]{baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()}); ++ }); ++ public static final BaseBlockPosition ZERO = new BaseBlockPosition(0, 0, 0); ++ private int a; ++ private int b; ++ private int e; ++ ++ public BaseBlockPosition(int i, int j, int k) { ++ this.a = i; ++ this.b = j; ++ this.e = k; ++ } ++ ++ public BaseBlockPosition(double d0, double d1, double d2) { ++ this(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2)); ++ } ++ ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } else if (!(object instanceof BaseBlockPosition)) { ++ return false; ++ } else { ++ BaseBlockPosition baseblockposition = (BaseBlockPosition) object; ++ ++ return this.getX() != baseblockposition.getX() ? false : (this.getY() != baseblockposition.getY() ? false : this.getZ() == baseblockposition.getZ()); ++ } ++ } ++ ++ public int hashCode() { ++ return (this.getY() + this.getZ() * 31) * 31 + this.getX(); ++ } ++ ++ public int compareTo(BaseBlockPosition baseblockposition) { ++ return this.getY() == baseblockposition.getY() ? (this.getZ() == baseblockposition.getZ() ? this.getX() - baseblockposition.getX() : this.getZ() - baseblockposition.getZ()) : this.getY() - baseblockposition.getY(); ++ } ++ ++ public int getX() { ++ return this.a; ++ } ++ ++ public int getY() { ++ return this.b; ++ } ++ ++ public int getZ() { ++ return this.e; ++ } ++ ++ protected void o(int i) { ++ this.a = i; ++ } ++ ++ protected void p(int i) { ++ this.b = i; ++ } ++ ++ protected void q(int i) { ++ this.e = i; ++ } ++ ++ public BaseBlockPosition up() { ++ return this.up(1); ++ } ++ ++ public BaseBlockPosition up(int i) { ++ return this.shift(EnumDirection.UP, i); ++ } ++ ++ public BaseBlockPosition down() { ++ return this.down(1); ++ } ++ ++ public BaseBlockPosition down(int i) { ++ return this.shift(EnumDirection.DOWN, i); ++ } ++ ++ public BaseBlockPosition shift(EnumDirection enumdirection, int i) { ++ return i == 0 ? this : new BaseBlockPosition(this.getX() + enumdirection.getAdjacentX() * i, this.getY() + enumdirection.getAdjacentY() * i, this.getZ() + enumdirection.getAdjacentZ() * i); ++ } ++ ++ public BaseBlockPosition d(BaseBlockPosition baseblockposition) { ++ return new BaseBlockPosition(this.getY() * baseblockposition.getZ() - this.getZ() * baseblockposition.getY(), this.getZ() * baseblockposition.getX() - this.getX() * baseblockposition.getZ(), this.getX() * baseblockposition.getY() - this.getY() * baseblockposition.getX()); ++ } ++ ++ public boolean a(BaseBlockPosition baseblockposition, double d0) { ++ return this.distanceSquared((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ(), false) < d0 * d0; ++ } ++ ++ public boolean a(IPosition iposition, double d0) { ++ return this.distanceSquared(iposition.getX(), iposition.getY(), iposition.getZ(), true) < d0 * d0; ++ } ++ ++ public double j(BaseBlockPosition baseblockposition) { ++ return this.distanceSquared((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ(), true); ++ } ++ ++ public double a(IPosition iposition, boolean flag) { ++ return this.distanceSquared(iposition.getX(), iposition.getY(), iposition.getZ(), flag); ++ } ++ ++ public double distanceSquared(double d0, double d1, double d2, boolean flag) { ++ double d3 = flag ? 0.5D : 0.0D; ++ double d4 = (double) this.getX() + d3 - d0; ++ double d5 = (double) this.getY() + d3 - d1; ++ double d6 = (double) this.getZ() + d3 - d2; ++ ++ return d4 * d4 + d5 * d5 + d6 * d6; ++ } ++ ++ public int k(BaseBlockPosition baseblockposition) { ++ float f = (float) Math.abs(baseblockposition.getX() - this.getX()); ++ float f1 = (float) Math.abs(baseblockposition.getY() - this.getY()); ++ float f2 = (float) Math.abs(baseblockposition.getZ() - this.getZ()); ++ ++ return (int) (f + f1 + f2); ++ } ++ ++ public int a(EnumDirection.EnumAxis enumdirection_enumaxis) { ++ return enumdirection_enumaxis.a(this.a, this.b, this.e); ++ } ++ ++ public String toString() { ++ return MoreObjects.toStringHelper(this).add("x", this.getX()).add("y", this.getY()).add("z", this.getZ()).toString(); ++ } ++} +diff --git a/src/main/java/net/minecraft/core/BlockPosition.java b/src/main/java/net/minecraft/core/BlockPosition.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f85889a232998520761731a17f3d293d3360fe2c +--- /dev/null ++++ b/src/main/java/net/minecraft/core/BlockPosition.java +@@ -0,0 +1,519 @@ ++package net.minecraft.core; ++ ++import com.google.common.collect.AbstractIterator; ++import com.mojang.serialization.Codec; ++import java.util.Optional; ++import java.util.Random; ++import java.util.function.Predicate; ++import java.util.stream.IntStream; ++import java.util.stream.Stream; ++import java.util.stream.StreamSupport; ++import javax.annotation.concurrent.Immutable; ++import net.minecraft.SystemUtils; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.level.block.EnumBlockRotation; ++import net.minecraft.world.level.levelgen.structure.StructureBoundingBox; ++import net.minecraft.world.phys.AxisAlignedBB; ++import net.minecraft.world.phys.Vec3D; ++import org.apache.commons.lang3.Validate; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++@Immutable ++public class BlockPosition extends BaseBlockPosition { ++ ++ public static final Codec a = Codec.INT_STREAM.comapFlatMap((intstream) -> { ++ return SystemUtils.a(intstream, 3).map((aint) -> { ++ return new BlockPosition(aint[0], aint[1], aint[2]); ++ }); ++ }, (blockposition) -> { ++ return IntStream.of(new int[]{blockposition.getX(), blockposition.getY(), blockposition.getZ()}); ++ }).stable(); ++ private static final Logger LOGGER = LogManager.getLogger(); ++ public static final BlockPosition ZERO = new BlockPosition(0, 0, 0); ++ private static final int f = 1 + MathHelper.f(MathHelper.c(30000000)); ++ private static final int g = BlockPosition.f; ++ private static final int h = 64 - BlockPosition.f - BlockPosition.g; ++ private static final long i = (1L << BlockPosition.f) - 1L; ++ private static final long j = (1L << BlockPosition.h) - 1L; ++ private static final long k = (1L << BlockPosition.g) - 1L; ++ private static final int l = BlockPosition.h; ++ private static final int m = BlockPosition.h + BlockPosition.g; ++ ++ public BlockPosition(int i, int j, int k) { ++ super(i, j, k); ++ } ++ ++ public BlockPosition(double d0, double d1, double d2) { ++ super(d0, d1, d2); ++ } ++ ++ public BlockPosition(Vec3D vec3d) { ++ this(vec3d.x, vec3d.y, vec3d.z); ++ } ++ ++ public BlockPosition(IPosition iposition) { ++ this(iposition.getX(), iposition.getY(), iposition.getZ()); ++ } ++ ++ public BlockPosition(BaseBlockPosition baseblockposition) { ++ this(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); ++ } ++ ++ public static long a(long i, EnumDirection enumdirection) { ++ return a(i, enumdirection.getAdjacentX(), enumdirection.getAdjacentY(), enumdirection.getAdjacentZ()); ++ } ++ ++ public static long a(long i, int j, int k, int l) { ++ return a(b(i) + j, c(i) + k, d(i) + l); ++ } ++ ++ public static int b(long i) { ++ return (int) (i << 64 - BlockPosition.m - BlockPosition.f >> 64 - BlockPosition.f); ++ } ++ ++ public static int c(long i) { ++ return (int) (i << 64 - BlockPosition.h >> 64 - BlockPosition.h); ++ } ++ ++ public static int d(long i) { ++ return (int) (i << 64 - BlockPosition.l - BlockPosition.g >> 64 - BlockPosition.g); ++ } ++ ++ public static BlockPosition fromLong(long i) { ++ return new BlockPosition(b(i), c(i), d(i)); ++ } ++ ++ public long asLong() { ++ return a(this.getX(), this.getY(), this.getZ()); ++ } ++ ++ public static long a(int i, int j, int k) { ++ long l = 0L; ++ ++ l |= ((long) i & BlockPosition.i) << BlockPosition.m; ++ l |= ((long) j & BlockPosition.j) << 0; ++ l |= ((long) k & BlockPosition.k) << BlockPosition.l; ++ return l; ++ } ++ ++ public static long f(long i) { ++ return i & -16L; ++ } ++ ++ public BlockPosition a(double d0, double d1, double d2) { ++ return d0 == 0.0D && d1 == 0.0D && d2 == 0.0D ? this : new BlockPosition((double) this.getX() + d0, (double) this.getY() + d1, (double) this.getZ() + d2); ++ } ++ ++ public BlockPosition b(int i, int j, int k) { ++ return i == 0 && j == 0 && k == 0 ? this : new BlockPosition(this.getX() + i, this.getY() + j, this.getZ() + k); ++ } ++ ++ public BlockPosition a(BaseBlockPosition baseblockposition) { ++ return this.b(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); ++ } ++ ++ public BlockPosition b(BaseBlockPosition baseblockposition) { ++ return this.b(-baseblockposition.getX(), -baseblockposition.getY(), -baseblockposition.getZ()); ++ } ++ ++ @Override ++ public BlockPosition up() { ++ return this.shift(EnumDirection.UP); ++ } ++ ++ @Override ++ public BlockPosition up(int i) { ++ return this.shift(EnumDirection.UP, i); ++ } ++ ++ @Override ++ public BlockPosition down() { ++ return this.shift(EnumDirection.DOWN); ++ } ++ ++ @Override ++ public BlockPosition down(int i) { ++ return this.shift(EnumDirection.DOWN, i); ++ } ++ ++ public BlockPosition north() { ++ return this.shift(EnumDirection.NORTH); ++ } ++ ++ public BlockPosition north(int i) { ++ return this.shift(EnumDirection.NORTH, i); ++ } ++ ++ public BlockPosition south() { ++ return this.shift(EnumDirection.SOUTH); ++ } ++ ++ public BlockPosition south(int i) { ++ return this.shift(EnumDirection.SOUTH, i); ++ } ++ ++ public BlockPosition west() { ++ return this.shift(EnumDirection.WEST); ++ } ++ ++ public BlockPosition west(int i) { ++ return this.shift(EnumDirection.WEST, i); ++ } ++ ++ public BlockPosition east() { ++ return this.shift(EnumDirection.EAST); ++ } ++ ++ public BlockPosition east(int i) { ++ return this.shift(EnumDirection.EAST, i); ++ } ++ ++ public BlockPosition shift(EnumDirection enumdirection) { ++ return new BlockPosition(this.getX() + enumdirection.getAdjacentX(), this.getY() + enumdirection.getAdjacentY(), this.getZ() + enumdirection.getAdjacentZ()); ++ } ++ ++ @Override ++ public BlockPosition shift(EnumDirection enumdirection, int i) { ++ return i == 0 ? this : new BlockPosition(this.getX() + enumdirection.getAdjacentX() * i, this.getY() + enumdirection.getAdjacentY() * i, this.getZ() + enumdirection.getAdjacentZ() * i); ++ } ++ ++ public BlockPosition a(EnumDirection.EnumAxis enumdirection_enumaxis, int i) { ++ if (i == 0) { ++ return this; ++ } else { ++ int j = enumdirection_enumaxis == EnumDirection.EnumAxis.X ? i : 0; ++ int k = enumdirection_enumaxis == EnumDirection.EnumAxis.Y ? i : 0; ++ int l = enumdirection_enumaxis == EnumDirection.EnumAxis.Z ? i : 0; ++ ++ return new BlockPosition(this.getX() + j, this.getY() + k, this.getZ() + l); ++ } ++ } ++ ++ public BlockPosition a(EnumBlockRotation enumblockrotation) { ++ switch (enumblockrotation) { ++ case NONE: ++ default: ++ return this; ++ case CLOCKWISE_90: ++ return new BlockPosition(-this.getZ(), this.getY(), this.getX()); ++ case CLOCKWISE_180: ++ return new BlockPosition(-this.getX(), this.getY(), -this.getZ()); ++ case COUNTERCLOCKWISE_90: ++ return new BlockPosition(this.getZ(), this.getY(), -this.getX()); ++ } ++ } ++ ++ @Override ++ public BlockPosition d(BaseBlockPosition baseblockposition) { ++ return new BlockPosition(this.getY() * baseblockposition.getZ() - this.getZ() * baseblockposition.getY(), this.getZ() * baseblockposition.getX() - this.getX() * baseblockposition.getZ(), this.getX() * baseblockposition.getY() - this.getY() * baseblockposition.getX()); ++ } ++ ++ public BlockPosition immutableCopy() { ++ return this; ++ } ++ ++ public BlockPosition.MutableBlockPosition i() { ++ return new BlockPosition.MutableBlockPosition(this.getX(), this.getY(), this.getZ()); ++ } ++ ++ public static Iterable a(Random random, int i, int j, int k, int l, int i1, int j1, int k1) { ++ int l1 = i1 - j + 1; ++ int i2 = j1 - k + 1; ++ int j2 = k1 - l + 1; ++ ++ return () -> { ++ return new AbstractIterator() { ++ final BlockPosition.MutableBlockPosition a = new BlockPosition.MutableBlockPosition(); ++ int b = i; ++ ++ protected BlockPosition computeNext() { ++ if (this.b <= 0) { ++ return (BlockPosition) this.endOfData(); ++ } else { ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = this.a.d(j + random.nextInt(l1), k + random.nextInt(i2), l + random.nextInt(j2)); ++ ++ --this.b; ++ return blockposition_mutableblockposition; ++ } ++ } ++ }; ++ }; ++ } ++ ++ public static Iterable a(BlockPosition blockposition, int i, int j, int k) { ++ int l = i + j + k; ++ int i1 = blockposition.getX(); ++ int j1 = blockposition.getY(); ++ int k1 = blockposition.getZ(); ++ ++ return () -> { ++ return new AbstractIterator() { ++ private final BlockPosition.MutableBlockPosition h = new BlockPosition.MutableBlockPosition(); ++ private int i; ++ private int j; ++ private int k; ++ private int l; ++ private int m; ++ private boolean n; ++ ++ protected BlockPosition computeNext() { ++ if (this.n) { ++ this.n = false; ++ this.h.q(k1 - (this.h.getZ() - k1)); ++ return this.h; ++ } else { ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition; ++ ++ for (blockposition_mutableblockposition = null; blockposition_mutableblockposition == null; ++this.m) { ++ if (this.m > this.k) { ++ ++this.l; ++ if (this.l > this.j) { ++ ++this.i; ++ if (this.i > l) { ++ return (BlockPosition) this.endOfData(); ++ } ++ ++ this.j = Math.min(i, this.i); ++ this.l = -this.j; ++ } ++ ++ this.k = Math.min(j, this.i - Math.abs(this.l)); ++ this.m = -this.k; ++ } ++ ++ int l1 = this.l; ++ int i2 = this.m; ++ int j2 = this.i - Math.abs(l1) - Math.abs(i2); ++ ++ if (j2 <= k) { ++ this.n = j2 != 0; ++ blockposition_mutableblockposition = this.h.d(i1 + l1, j1 + i2, k1 + j2); ++ } ++ } ++ ++ return blockposition_mutableblockposition; ++ } ++ } ++ }; ++ }; ++ } ++ ++ public static Optional a(BlockPosition blockposition, int i, int j, Predicate predicate) { ++ return b(blockposition, i, j, i).filter(predicate).findFirst(); ++ } ++ ++ public static Stream b(BlockPosition blockposition, int i, int j, int k) { ++ return StreamSupport.stream(a(blockposition, i, j, k).spliterator(), false); ++ } ++ ++ public static Iterable a(BlockPosition blockposition, BlockPosition blockposition1) { ++ return b(Math.min(blockposition.getX(), blockposition1.getX()), Math.min(blockposition.getY(), blockposition1.getY()), Math.min(blockposition.getZ(), blockposition1.getZ()), Math.max(blockposition.getX(), blockposition1.getX()), Math.max(blockposition.getY(), blockposition1.getY()), Math.max(blockposition.getZ(), blockposition1.getZ())); ++ } ++ ++ public static Stream b(BlockPosition blockposition, BlockPosition blockposition1) { ++ return StreamSupport.stream(a(blockposition, blockposition1).spliterator(), false); ++ } ++ ++ public static Stream a(StructureBoundingBox structureboundingbox) { ++ return a(Math.min(structureboundingbox.a, structureboundingbox.d), Math.min(structureboundingbox.b, structureboundingbox.e), Math.min(structureboundingbox.c, structureboundingbox.f), Math.max(structureboundingbox.a, structureboundingbox.d), Math.max(structureboundingbox.b, structureboundingbox.e), Math.max(structureboundingbox.c, structureboundingbox.f)); ++ } ++ ++ public static Stream a(AxisAlignedBB axisalignedbb) { ++ return a(MathHelper.floor(axisalignedbb.minX), MathHelper.floor(axisalignedbb.minY), MathHelper.floor(axisalignedbb.minZ), MathHelper.floor(axisalignedbb.maxX), MathHelper.floor(axisalignedbb.maxY), MathHelper.floor(axisalignedbb.maxZ)); ++ } ++ ++ public static Stream a(int i, int j, int k, int l, int i1, int j1) { ++ return StreamSupport.stream(b(i, j, k, l, i1, j1).spliterator(), false); ++ } ++ ++ public static Iterable b(int i, int j, int k, int l, int i1, int j1) { ++ int k1 = l - i + 1; ++ int l1 = i1 - j + 1; ++ int i2 = j1 - k + 1; ++ int j2 = k1 * l1 * i2; ++ ++ return () -> { ++ return new AbstractIterator() { ++ private final BlockPosition.MutableBlockPosition g = new BlockPosition.MutableBlockPosition(); ++ private int h; ++ ++ protected BlockPosition computeNext() { ++ if (this.h == j2) { ++ return (BlockPosition) this.endOfData(); ++ } else { ++ int k2 = this.h % k1; ++ int l2 = this.h / k1; ++ int i3 = l2 % l1; ++ int j3 = l2 / l1; ++ ++ ++this.h; ++ return this.g.d(i + k2, j + i3, k + j3); ++ } ++ } ++ }; ++ }; ++ } ++ ++ public static Iterable a(BlockPosition blockposition, int i, EnumDirection enumdirection, EnumDirection enumdirection1) { ++ Validate.validState(enumdirection.n() != enumdirection1.n(), "The two directions cannot be on the same axis", new Object[0]); ++ return () -> { ++ return new AbstractIterator() { ++ private final EnumDirection[] e = new EnumDirection[]{enumdirection, enumdirection1, enumdirection.opposite(), enumdirection1.opposite()}; ++ private final BlockPosition.MutableBlockPosition f = blockposition.i().c(enumdirection1); ++ private final int g = 4 * i; ++ private int h = -1; ++ private int i; ++ private int j; ++ private int k; ++ private int l; ++ private int m; ++ ++ { ++ this.k = this.f.getX(); ++ this.l = this.f.getY(); ++ this.m = this.f.getZ(); ++ } ++ ++ protected BlockPosition.MutableBlockPosition computeNext() { ++ this.f.d(this.k, this.l, this.m).c(this.e[(this.h + 4) % 4]); ++ this.k = this.f.getX(); ++ this.l = this.f.getY(); ++ this.m = this.f.getZ(); ++ if (this.j >= this.i) { ++ if (this.h >= this.g) { ++ return (BlockPosition.MutableBlockPosition) this.endOfData(); ++ } ++ ++ ++this.h; ++ this.j = 0; ++ this.i = this.h / 2 + 1; ++ } ++ ++ ++this.j; ++ return this.f; ++ } ++ }; ++ }; ++ } ++ ++ public static class MutableBlockPosition extends BlockPosition { ++ ++ public MutableBlockPosition() { ++ this(0, 0, 0); ++ } ++ ++ public MutableBlockPosition(int i, int j, int k) { ++ super(i, j, k); ++ } ++ ++ public MutableBlockPosition(double d0, double d1, double d2) { ++ this(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2)); ++ } ++ ++ @Override ++ public BlockPosition a(double d0, double d1, double d2) { ++ return super.a(d0, d1, d2).immutableCopy(); ++ } ++ ++ @Override ++ public BlockPosition b(int i, int j, int k) { ++ return super.b(i, j, k).immutableCopy(); ++ } ++ ++ @Override ++ public BlockPosition shift(EnumDirection enumdirection, int i) { ++ return super.shift(enumdirection, i).immutableCopy(); ++ } ++ ++ @Override ++ public BlockPosition a(EnumDirection.EnumAxis enumdirection_enumaxis, int i) { ++ return super.a(enumdirection_enumaxis, i).immutableCopy(); ++ } ++ ++ @Override ++ public BlockPosition a(EnumBlockRotation enumblockrotation) { ++ return super.a(enumblockrotation).immutableCopy(); ++ } ++ ++ public BlockPosition.MutableBlockPosition d(int i, int j, int k) { ++ this.o(i); ++ this.p(j); ++ this.q(k); ++ return this; ++ } ++ ++ public BlockPosition.MutableBlockPosition c(double d0, double d1, double d2) { ++ return this.d(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2)); ++ } ++ ++ public BlockPosition.MutableBlockPosition g(BaseBlockPosition baseblockposition) { ++ return this.d(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); ++ } ++ ++ public BlockPosition.MutableBlockPosition g(long i) { ++ return this.d(b(i), c(i), d(i)); ++ } ++ ++ public BlockPosition.MutableBlockPosition a(EnumAxisCycle enumaxiscycle, int i, int j, int k) { ++ return this.d(enumaxiscycle.a(i, j, k, EnumDirection.EnumAxis.X), enumaxiscycle.a(i, j, k, EnumDirection.EnumAxis.Y), enumaxiscycle.a(i, j, k, EnumDirection.EnumAxis.Z)); ++ } ++ ++ public BlockPosition.MutableBlockPosition a(BaseBlockPosition baseblockposition, EnumDirection enumdirection) { ++ return this.d(baseblockposition.getX() + enumdirection.getAdjacentX(), baseblockposition.getY() + enumdirection.getAdjacentY(), baseblockposition.getZ() + enumdirection.getAdjacentZ()); ++ } ++ ++ public BlockPosition.MutableBlockPosition a(BaseBlockPosition baseblockposition, int i, int j, int k) { ++ return this.d(baseblockposition.getX() + i, baseblockposition.getY() + j, baseblockposition.getZ() + k); ++ } ++ ++ public BlockPosition.MutableBlockPosition c(EnumDirection enumdirection) { ++ return this.c(enumdirection, 1); ++ } ++ ++ public BlockPosition.MutableBlockPosition c(EnumDirection enumdirection, int i) { ++ return this.d(this.getX() + enumdirection.getAdjacentX() * i, this.getY() + enumdirection.getAdjacentY() * i, this.getZ() + enumdirection.getAdjacentZ() * i); ++ } ++ ++ public BlockPosition.MutableBlockPosition e(int i, int j, int k) { ++ return this.d(this.getX() + i, this.getY() + j, this.getZ() + k); ++ } ++ ++ public BlockPosition.MutableBlockPosition h(BaseBlockPosition baseblockposition) { ++ return this.d(this.getX() + baseblockposition.getX(), this.getY() + baseblockposition.getY(), this.getZ() + baseblockposition.getZ()); ++ } ++ ++ public BlockPosition.MutableBlockPosition a(EnumDirection.EnumAxis enumdirection_enumaxis, int i, int j) { ++ switch (enumdirection_enumaxis) { ++ case X: ++ return this.d(MathHelper.clamp(this.getX(), i, j), this.getY(), this.getZ()); ++ case Y: ++ return this.d(this.getX(), MathHelper.clamp(this.getY(), i, j), this.getZ()); ++ case Z: ++ return this.d(this.getX(), this.getY(), MathHelper.clamp(this.getZ(), i, j)); ++ default: ++ throw new IllegalStateException("Unable to clamp axis " + enumdirection_enumaxis); ++ } ++ } ++ ++ @Override ++ public void o(int i) { ++ super.o(i); ++ } ++ ++ @Override ++ public void p(int i) { ++ super.p(i); ++ } ++ ++ @Override ++ public void q(int i) { ++ super.q(i); ++ } ++ ++ @Override ++ public BlockPosition immutableCopy() { ++ return new BlockPosition(this); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/core/EnumDirection.java b/src/main/java/net/minecraft/core/EnumDirection.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a699005582293326076eaa80655c5343e6c22ff0 +--- /dev/null ++++ b/src/main/java/net/minecraft/core/EnumDirection.java +@@ -0,0 +1,410 @@ ++package net.minecraft.core; ++ ++import com.google.common.collect.Iterators; ++import com.mojang.serialization.Codec; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import java.util.Arrays; ++import java.util.Comparator; ++import java.util.Iterator; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Random; ++import java.util.function.Predicate; ++import java.util.stream.Collectors; ++import java.util.stream.Stream; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.util.INamable; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.Entity; ++ ++public enum EnumDirection implements INamable { ++ ++ DOWN(0, 1, -1, "down", EnumDirection.EnumAxisDirection.NEGATIVE, EnumDirection.EnumAxis.Y, new BaseBlockPosition(0, -1, 0)), UP(1, 0, -1, "up", EnumDirection.EnumAxisDirection.POSITIVE, EnumDirection.EnumAxis.Y, new BaseBlockPosition(0, 1, 0)), NORTH(2, 3, 2, "north", EnumDirection.EnumAxisDirection.NEGATIVE, EnumDirection.EnumAxis.Z, new BaseBlockPosition(0, 0, -1)), SOUTH(3, 2, 0, "south", EnumDirection.EnumAxisDirection.POSITIVE, EnumDirection.EnumAxis.Z, new BaseBlockPosition(0, 0, 1)), WEST(4, 5, 1, "west", EnumDirection.EnumAxisDirection.NEGATIVE, EnumDirection.EnumAxis.X, new BaseBlockPosition(-1, 0, 0)), EAST(5, 4, 3, "east", EnumDirection.EnumAxisDirection.POSITIVE, EnumDirection.EnumAxis.X, new BaseBlockPosition(1, 0, 0)); ++ ++ private final int g; ++ private final int h; ++ private final int i; ++ private final String j; ++ private final EnumDirection.EnumAxis k; ++ private final EnumDirection.EnumAxisDirection l; ++ private final BaseBlockPosition m; ++ private static final EnumDirection[] n = values(); ++ private static final Map o = (Map) Arrays.stream(EnumDirection.n).collect(Collectors.toMap(EnumDirection::m, (enumdirection) -> { ++ return enumdirection; ++ })); ++ private static final EnumDirection[] p = (EnumDirection[]) Arrays.stream(EnumDirection.n).sorted(Comparator.comparingInt((enumdirection) -> { ++ return enumdirection.g; ++ })).toArray((i) -> { ++ return new EnumDirection[i]; ++ }); ++ private static final EnumDirection[] q = (EnumDirection[]) Arrays.stream(EnumDirection.n).filter((enumdirection) -> { ++ return enumdirection.n().d(); ++ }).sorted(Comparator.comparingInt((enumdirection) -> { ++ return enumdirection.i; ++ })).toArray((i) -> { ++ return new EnumDirection[i]; ++ }); ++ private static final Long2ObjectMap r = (Long2ObjectMap) Arrays.stream(EnumDirection.n).collect(Collectors.toMap((enumdirection) -> { ++ return (new BlockPosition(enumdirection.p())).asLong(); ++ }, (enumdirection) -> { ++ return enumdirection; ++ }, (enumdirection, enumdirection1) -> { ++ throw new IllegalArgumentException("Duplicate keys"); ++ }, Long2ObjectOpenHashMap::new)); ++ ++ private EnumDirection(int i, int j, int k, String s, EnumDirection.EnumAxisDirection enumdirection_enumaxisdirection, EnumDirection.EnumAxis enumdirection_enumaxis, BaseBlockPosition baseblockposition) { ++ this.g = i; ++ this.i = k; ++ this.h = j; ++ this.j = s; ++ this.k = enumdirection_enumaxis; ++ this.l = enumdirection_enumaxisdirection; ++ this.m = baseblockposition; ++ } ++ ++ public static EnumDirection[] a(Entity entity) { ++ float f = entity.g(1.0F) * 0.017453292F; ++ float f1 = -entity.h(1.0F) * 0.017453292F; ++ float f2 = MathHelper.sin(f); ++ float f3 = MathHelper.cos(f); ++ float f4 = MathHelper.sin(f1); ++ float f5 = MathHelper.cos(f1); ++ boolean flag = f4 > 0.0F; ++ boolean flag1 = f2 < 0.0F; ++ boolean flag2 = f5 > 0.0F; ++ float f6 = flag ? f4 : -f4; ++ float f7 = flag1 ? -f2 : f2; ++ float f8 = flag2 ? f5 : -f5; ++ float f9 = f6 * f3; ++ float f10 = f8 * f3; ++ EnumDirection enumdirection = flag ? EnumDirection.EAST : EnumDirection.WEST; ++ EnumDirection enumdirection1 = flag1 ? EnumDirection.UP : EnumDirection.DOWN; ++ EnumDirection enumdirection2 = flag2 ? EnumDirection.SOUTH : EnumDirection.NORTH; ++ ++ return f6 > f8 ? (f7 > f9 ? a(enumdirection1, enumdirection, enumdirection2) : (f10 > f7 ? a(enumdirection, enumdirection2, enumdirection1) : a(enumdirection, enumdirection1, enumdirection2))) : (f7 > f10 ? a(enumdirection1, enumdirection2, enumdirection) : (f9 > f7 ? a(enumdirection2, enumdirection, enumdirection1) : a(enumdirection2, enumdirection1, enumdirection))); ++ } ++ ++ private static EnumDirection[] a(EnumDirection enumdirection, EnumDirection enumdirection1, EnumDirection enumdirection2) { ++ return new EnumDirection[]{enumdirection, enumdirection1, enumdirection2, enumdirection2.opposite(), enumdirection1.opposite(), enumdirection.opposite()}; ++ } ++ ++ public int c() { ++ return this.g; ++ } ++ ++ public int get2DRotationValue() { ++ return this.i; ++ } ++ ++ public EnumDirection.EnumAxisDirection e() { ++ return this.l; ++ } ++ ++ public EnumDirection opposite() { ++ return fromType1(this.h); ++ } ++ ++ public EnumDirection g() { ++ switch (this) { ++ case NORTH: ++ return EnumDirection.EAST; ++ case SOUTH: ++ return EnumDirection.WEST; ++ case WEST: ++ return EnumDirection.NORTH; ++ case EAST: ++ return EnumDirection.SOUTH; ++ default: ++ throw new IllegalStateException("Unable to get Y-rotated facing of " + this); ++ } ++ } ++ ++ public EnumDirection h() { ++ switch (this) { ++ case NORTH: ++ return EnumDirection.WEST; ++ case SOUTH: ++ return EnumDirection.EAST; ++ case WEST: ++ return EnumDirection.SOUTH; ++ case EAST: ++ return EnumDirection.NORTH; ++ default: ++ throw new IllegalStateException("Unable to get CCW facing of " + this); ++ } ++ } ++ ++ public int getAdjacentX() { ++ return this.m.getX(); ++ } ++ ++ public int getAdjacentY() { ++ return this.m.getY(); ++ } ++ ++ public int getAdjacentZ() { ++ return this.m.getZ(); ++ } ++ ++ public String m() { ++ return this.j; ++ } ++ ++ public EnumDirection.EnumAxis n() { ++ return this.k; ++ } ++ ++ public static EnumDirection fromType1(int i) { ++ return EnumDirection.p[MathHelper.a(i % EnumDirection.p.length)]; ++ } ++ ++ public static EnumDirection fromType2(int i) { ++ return EnumDirection.q[MathHelper.a(i % EnumDirection.q.length)]; ++ } ++ ++ @Nullable ++ public static EnumDirection a(int i, int j, int k) { ++ return (EnumDirection) EnumDirection.r.get(BlockPosition.a(i, j, k)); ++ } ++ ++ public static EnumDirection fromAngle(double d0) { ++ return fromType2(MathHelper.floor(d0 / 90.0D + 0.5D) & 3); ++ } ++ ++ public static EnumDirection a(EnumDirection.EnumAxis enumdirection_enumaxis, EnumDirection.EnumAxisDirection enumdirection_enumaxisdirection) { ++ switch (enumdirection_enumaxis) { ++ case X: ++ return enumdirection_enumaxisdirection == EnumDirection.EnumAxisDirection.POSITIVE ? EnumDirection.EAST : EnumDirection.WEST; ++ case Y: ++ return enumdirection_enumaxisdirection == EnumDirection.EnumAxisDirection.POSITIVE ? EnumDirection.UP : EnumDirection.DOWN; ++ case Z: ++ default: ++ return enumdirection_enumaxisdirection == EnumDirection.EnumAxisDirection.POSITIVE ? EnumDirection.SOUTH : EnumDirection.NORTH; ++ } ++ } ++ ++ public float o() { ++ return (float) ((this.i & 3) * 90); ++ } ++ ++ public static EnumDirection a(Random random) { ++ return (EnumDirection) SystemUtils.a((Object[]) EnumDirection.n, random); ++ } ++ ++ public static EnumDirection a(double d0, double d1, double d2) { ++ return a((float) d0, (float) d1, (float) d2); ++ } ++ ++ public static EnumDirection a(float f, float f1, float f2) { ++ EnumDirection enumdirection = EnumDirection.NORTH; ++ float f3 = Float.MIN_VALUE; ++ EnumDirection[] aenumdirection = EnumDirection.n; ++ int i = aenumdirection.length; ++ ++ for (int j = 0; j < i; ++j) { ++ EnumDirection enumdirection1 = aenumdirection[j]; ++ float f4 = f * (float) enumdirection1.m.getX() + f1 * (float) enumdirection1.m.getY() + f2 * (float) enumdirection1.m.getZ(); ++ ++ if (f4 > f3) { ++ f3 = f4; ++ enumdirection = enumdirection1; ++ } ++ } ++ ++ return enumdirection; ++ } ++ ++ public String toString() { ++ return this.j; ++ } ++ ++ @Override ++ public String getName() { ++ return this.j; ++ } ++ ++ public static EnumDirection a(EnumDirection.EnumAxisDirection enumdirection_enumaxisdirection, EnumDirection.EnumAxis enumdirection_enumaxis) { ++ EnumDirection[] aenumdirection = EnumDirection.n; ++ int i = aenumdirection.length; ++ ++ for (int j = 0; j < i; ++j) { ++ EnumDirection enumdirection = aenumdirection[j]; ++ ++ if (enumdirection.e() == enumdirection_enumaxisdirection && enumdirection.n() == enumdirection_enumaxis) { ++ return enumdirection; ++ } ++ } ++ ++ throw new IllegalArgumentException("No such direction: " + enumdirection_enumaxisdirection + " " + enumdirection_enumaxis); ++ } ++ ++ public BaseBlockPosition p() { ++ return this.m; ++ } ++ ++ public boolean a(float f) { ++ float f1 = f * 0.017453292F; ++ float f2 = -MathHelper.sin(f1); ++ float f3 = MathHelper.cos(f1); ++ ++ return (float) this.m.getX() * f2 + (float) this.m.getZ() * f3 > 0.0F; ++ } ++ ++ public static enum EnumDirectionLimit implements Iterable, Predicate { ++ ++ HORIZONTAL(new EnumDirection[]{EnumDirection.NORTH, EnumDirection.EAST, EnumDirection.SOUTH, EnumDirection.WEST}, new EnumDirection.EnumAxis[]{EnumDirection.EnumAxis.X, EnumDirection.EnumAxis.Z}), VERTICAL(new EnumDirection[]{EnumDirection.UP, EnumDirection.DOWN}, new EnumDirection.EnumAxis[]{EnumDirection.EnumAxis.Y}); ++ ++ private final EnumDirection[] c; ++ private final EnumDirection.EnumAxis[] d; ++ ++ private EnumDirectionLimit(EnumDirection[] aenumdirection, EnumDirection.EnumAxis[] aenumdirection_enumaxis) { ++ this.c = aenumdirection; ++ this.d = aenumdirection_enumaxis; ++ } ++ ++ public EnumDirection a(Random random) { ++ return (EnumDirection) SystemUtils.a((Object[]) this.c, random); ++ } ++ ++ public EnumDirection.EnumAxis b(Random random) { ++ return (EnumDirection.EnumAxis) SystemUtils.a((Object[]) this.d, random); ++ } ++ ++ public boolean test(@Nullable EnumDirection enumdirection) { ++ return enumdirection != null && enumdirection.n().e() == this; ++ } ++ ++ public Iterator iterator() { ++ return Iterators.forArray(this.c); ++ } ++ ++ public Stream a() { ++ return Arrays.stream(this.c); ++ } ++ } ++ ++ public static enum EnumAxisDirection { ++ ++ POSITIVE(1, "Towards positive"), NEGATIVE(-1, "Towards negative"); ++ ++ private final int c; ++ private final String d; ++ ++ private EnumAxisDirection(int i, String s) { ++ this.c = i; ++ this.d = s; ++ } ++ ++ public int a() { ++ return this.c; ++ } ++ ++ public String toString() { ++ return this.d; ++ } ++ ++ public EnumDirection.EnumAxisDirection c() { ++ return this == EnumDirection.EnumAxisDirection.POSITIVE ? EnumDirection.EnumAxisDirection.NEGATIVE : EnumDirection.EnumAxisDirection.POSITIVE; ++ } ++ } ++ ++ public static enum EnumAxis implements INamable, Predicate { ++ ++ X("x") { ++ @Override ++ public int a(int i, int j, int k) { ++ return i; ++ } ++ ++ @Override ++ public double a(double d0, double d1, double d2) { ++ return d0; ++ } ++ }, ++ Y("y") { ++ @Override ++ public int a(int i, int j, int k) { ++ return j; ++ } ++ ++ @Override ++ public double a(double d0, double d1, double d2) { ++ return d1; ++ } ++ }, ++ Z("z") { ++ @Override ++ public int a(int i, int j, int k) { ++ return k; ++ } ++ ++ @Override ++ public double a(double d0, double d1, double d2) { ++ return d2; ++ } ++ }; ++ ++ private static final EnumDirection.EnumAxis[] e = values(); ++ public static final Codec d = INamable.a(EnumDirection.EnumAxis::values, EnumDirection.EnumAxis::a); ++ private static final Map f = (Map) Arrays.stream(EnumDirection.EnumAxis.e).collect(Collectors.toMap(EnumDirection.EnumAxis::b, (enumdirection_enumaxis) -> { ++ return enumdirection_enumaxis; ++ })); ++ private final String g; ++ ++ private EnumAxis(String s) { ++ this.g = s; ++ } ++ ++ @Nullable ++ public static EnumDirection.EnumAxis a(String s) { ++ return (EnumDirection.EnumAxis) EnumDirection.EnumAxis.f.get(s.toLowerCase(Locale.ROOT)); ++ } ++ ++ public String b() { ++ return this.g; ++ } ++ ++ public boolean c() { ++ return this == EnumDirection.EnumAxis.Y; ++ } ++ ++ public boolean d() { ++ return this == EnumDirection.EnumAxis.X || this == EnumDirection.EnumAxis.Z; ++ } ++ ++ public String toString() { ++ return this.g; ++ } ++ ++ public static EnumDirection.EnumAxis a(Random random) { ++ return (EnumDirection.EnumAxis) SystemUtils.a((Object[]) EnumDirection.EnumAxis.e, random); ++ } ++ ++ public boolean test(@Nullable EnumDirection enumdirection) { ++ return enumdirection != null && enumdirection.n() == this; ++ } ++ ++ public EnumDirection.EnumDirectionLimit e() { ++ switch (this) { ++ case X: ++ case Z: ++ return EnumDirection.EnumDirectionLimit.HORIZONTAL; ++ case Y: ++ return EnumDirection.EnumDirectionLimit.VERTICAL; ++ default: ++ throw new Error("Someone's been tampering with the universe!"); ++ } ++ } ++ ++ @Override ++ public String getName() { ++ return this.g; ++ } ++ ++ public abstract int a(int i, int j, int k); ++ ++ public abstract double a(double d0, double d1, double d2); ++ } ++} +diff --git a/src/main/java/net/minecraft/core/IRegistry.java b/src/main/java/net/minecraft/core/IRegistry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3e9ebeffdf66f8a959630b344149d17137c6901c +--- /dev/null ++++ b/src/main/java/net/minecraft/core/IRegistry.java +@@ -0,0 +1,448 @@ ++package net.minecraft.core; ++ ++import com.google.common.collect.Maps; ++import com.mojang.datafixers.util.Pair; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.DataResult; ++import com.mojang.serialization.DynamicOps; ++import com.mojang.serialization.Keyable; ++import com.mojang.serialization.Lifecycle; ++import java.util.Map; ++import java.util.Map.Entry; ++import java.util.Optional; ++import java.util.Set; ++import java.util.function.Supplier; ++import java.util.stream.Stream; ++import java.util.stream.StreamSupport; ++import javax.annotation.Nullable; ++import net.minecraft.SharedConstants; ++import net.minecraft.core.particles.Particle; ++import net.minecraft.core.particles.Particles; ++import net.minecraft.data.RegistryGeneration; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.sounds.SoundEffect; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.stats.StatisticList; ++import net.minecraft.stats.StatisticWrapper; ++import net.minecraft.world.effect.MobEffectList; ++import net.minecraft.world.effect.MobEffects; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.ai.attributes.AttributeBase; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import net.minecraft.world.entity.ai.sensing.SensorType; ++import net.minecraft.world.entity.ai.village.poi.VillagePlaceType; ++import net.minecraft.world.entity.decoration.Paintings; ++import net.minecraft.world.entity.npc.VillagerProfession; ++import net.minecraft.world.entity.npc.VillagerType; ++import net.minecraft.world.entity.schedule.Activity; ++import net.minecraft.world.entity.schedule.Schedule; ++import net.minecraft.world.inventory.Containers; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.item.alchemy.PotionRegistry; ++import net.minecraft.world.item.alchemy.Potions; ++import net.minecraft.world.item.crafting.RecipeSerializer; ++import net.minecraft.world.item.crafting.Recipes; ++import net.minecraft.world.item.enchantment.Enchantment; ++import net.minecraft.world.item.enchantment.Enchantments; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.biome.BiomeBase; ++import net.minecraft.world.level.biome.WorldChunkManager; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.entity.TileEntityTypes; ++import net.minecraft.world.level.chunk.ChunkGenerator; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.dimension.DimensionManager; ++import net.minecraft.world.level.dimension.WorldDimension; ++import net.minecraft.world.level.levelgen.GeneratorSettingBase; ++import net.minecraft.world.level.levelgen.carver.WorldGenCarverAbstract; ++import net.minecraft.world.level.levelgen.carver.WorldGenCarverWrapper; ++import net.minecraft.world.level.levelgen.feature.StructureFeature; ++import net.minecraft.world.level.levelgen.feature.StructureGenerator; ++import net.minecraft.world.level.levelgen.feature.WorldGenFeatureConfigured; ++import net.minecraft.world.level.levelgen.feature.WorldGenFeatureStructurePieceType; ++import net.minecraft.world.level.levelgen.feature.WorldGenerator; ++import net.minecraft.world.level.levelgen.feature.blockplacers.WorldGenBlockPlacers; ++import net.minecraft.world.level.levelgen.feature.featuresize.FeatureSizeType; ++import net.minecraft.world.level.levelgen.feature.foliageplacers.WorldGenFoilagePlacers; ++import net.minecraft.world.level.levelgen.feature.stateproviders.WorldGenFeatureStateProviders; ++import net.minecraft.world.level.levelgen.feature.structures.WorldGenFeatureDefinedStructurePoolTemplate; ++import net.minecraft.world.level.levelgen.feature.structures.WorldGenFeatureDefinedStructurePools; ++import net.minecraft.world.level.levelgen.feature.treedecorators.WorldGenFeatureTrees; ++import net.minecraft.world.level.levelgen.feature.trunkplacers.TrunkPlacers; ++import net.minecraft.world.level.levelgen.placement.WorldGenDecorator; ++import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructureRuleTestType; ++import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructureStructureProcessorType; ++import net.minecraft.world.level.levelgen.structure.templatesystem.PosRuleTestType; ++import net.minecraft.world.level.levelgen.structure.templatesystem.ProcessorList; ++import net.minecraft.world.level.levelgen.surfacebuilders.WorldGenSurface; ++import net.minecraft.world.level.levelgen.surfacebuilders.WorldGenSurfaceComposite; ++import net.minecraft.world.level.material.FluidType; ++import net.minecraft.world.level.material.FluidTypes; ++import net.minecraft.world.level.storage.loot.entries.LootEntries; ++import net.minecraft.world.level.storage.loot.entries.LootEntryType; ++import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType; ++import net.minecraft.world.level.storage.loot.functions.LootItemFunctions; ++import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; ++import net.minecraft.world.level.storage.loot.predicates.LootItemConditions; ++import org.apache.commons.lang3.Validate; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public abstract class IRegistry implements Codec, Keyable, Registry { ++ ++ protected static final Logger LOGGER = LogManager.getLogger(); ++ private static final Map> a = Maps.newLinkedHashMap(); ++ public static final MinecraftKey d = new MinecraftKey("root"); ++ protected static final IRegistryWritable> e = new RegistryMaterials<>(a("root"), Lifecycle.experimental()); ++ public static final IRegistry> f = IRegistry.e; ++ public static final ResourceKey> g = a("sound_event"); ++ public static final ResourceKey> h = a("fluid"); ++ public static final ResourceKey> i = a("mob_effect"); ++ public static final ResourceKey> j = a("block"); ++ public static final ResourceKey> k = a("enchantment"); ++ public static final ResourceKey>> l = a("entity_type"); ++ public static final ResourceKey> m = a("item"); ++ public static final ResourceKey> n = a("potion"); ++ public static final ResourceKey>> o = a("particle_type"); ++ public static final ResourceKey>> p = a("block_entity_type"); ++ public static final ResourceKey> q = a("motive"); ++ public static final ResourceKey> r = a("custom_stat"); ++ public static final ResourceKey> s = a("chunk_status"); ++ public static final ResourceKey>> t = a("rule_test"); ++ public static final ResourceKey>> u = a("pos_rule_test"); ++ public static final ResourceKey>> v = a("menu"); ++ public static final ResourceKey>> w = a("recipe_type"); ++ public static final ResourceKey>> x = a("recipe_serializer"); ++ public static final ResourceKey> y = a("attribute"); ++ public static final ResourceKey>> z = a("stat_type"); ++ public static final ResourceKey> A = a("villager_type"); ++ public static final ResourceKey> B = a("villager_profession"); ++ public static final ResourceKey> C = a("point_of_interest_type"); ++ public static final ResourceKey>> D = a("memory_module_type"); ++ public static final ResourceKey>> E = a("sensor_type"); ++ public static final ResourceKey> F = a("schedule"); ++ public static final ResourceKey> G = a("activity"); ++ public static final ResourceKey> H = a("loot_pool_entry_type"); ++ public static final ResourceKey> I = a("loot_function_type"); ++ public static final ResourceKey> J = a("loot_condition_type"); ++ public static final ResourceKey> K = a("dimension_type"); ++ public static final ResourceKey> L = a("dimension"); ++ public static final ResourceKey> M = a("dimension"); ++ public static final IRegistry SOUND_EVENT = a(IRegistry.g, () -> { ++ return SoundEffects.ENTITY_ITEM_PICKUP; ++ }); ++ public static final RegistryBlocks FLUID = a(IRegistry.h, "empty", () -> { ++ return FluidTypes.EMPTY; ++ }); ++ public static final IRegistry MOB_EFFECT = a(IRegistry.i, () -> { ++ return MobEffects.LUCK; ++ }); ++ public static final RegistryBlocks BLOCK = a(IRegistry.j, "air", () -> { ++ return Blocks.AIR; ++ }); ++ public static final IRegistry ENCHANTMENT = a(IRegistry.k, () -> { ++ return Enchantments.LOOT_BONUS_BLOCKS; ++ }); ++ public static final RegistryBlocks> ENTITY_TYPE = a(IRegistry.l, "pig", () -> { ++ return EntityTypes.PIG; ++ }); ++ public static final RegistryBlocks ITEM = a(IRegistry.m, "air", () -> { ++ return Items.AIR; ++ }); ++ public static final RegistryBlocks POTION = a(IRegistry.n, "empty", () -> { ++ return Potions.EMPTY; ++ }); ++ public static final IRegistry> PARTICLE_TYPE = a(IRegistry.o, () -> { ++ return Particles.BLOCK; ++ }); ++ public static final IRegistry> BLOCK_ENTITY_TYPE = a(IRegistry.p, () -> { ++ return TileEntityTypes.FURNACE; ++ }); ++ public static final RegistryBlocks MOTIVE = a(IRegistry.q, "kebab", () -> { ++ return Paintings.a; ++ }); ++ public static final IRegistry CUSTOM_STAT = a(IRegistry.r, () -> { ++ return StatisticList.JUMP; ++ }); ++ public static final RegistryBlocks CHUNK_STATUS = a(IRegistry.s, "empty", () -> { ++ return ChunkStatus.EMPTY; ++ }); ++ public static final IRegistry> RULE_TEST = a(IRegistry.t, () -> { ++ return DefinedStructureRuleTestType.a; ++ }); ++ public static final IRegistry> POS_RULE_TEST = a(IRegistry.u, () -> { ++ return PosRuleTestType.a; ++ }); ++ public static final IRegistry> MENU = a(IRegistry.v, () -> { ++ return Containers.ANVIL; ++ }); ++ public static final IRegistry> RECIPE_TYPE = a(IRegistry.w, () -> { ++ return Recipes.CRAFTING; ++ }); ++ public static final IRegistry> RECIPE_SERIALIZER = a(IRegistry.x, () -> { ++ return RecipeSerializer.b; ++ }); ++ public static final IRegistry ATTRIBUTE = a(IRegistry.y, () -> { ++ return GenericAttributes.LUCK; ++ }); ++ public static final IRegistry> STATS = a(IRegistry.z, () -> { ++ return StatisticList.ITEM_USED; ++ }); ++ public static final RegistryBlocks VILLAGER_TYPE = a(IRegistry.A, "plains", () -> { ++ return VillagerType.PLAINS; ++ }); ++ public static final RegistryBlocks VILLAGER_PROFESSION = a(IRegistry.B, "none", () -> { ++ return VillagerProfession.NONE; ++ }); ++ public static final RegistryBlocks POINT_OF_INTEREST_TYPE = a(IRegistry.C, "unemployed", () -> { ++ return VillagePlaceType.c; ++ }); ++ public static final RegistryBlocks> MEMORY_MODULE_TYPE = a(IRegistry.D, "dummy", () -> { ++ return MemoryModuleType.DUMMY; ++ }); ++ public static final RegistryBlocks> SENSOR_TYPE = a(IRegistry.E, "dummy", () -> { ++ return SensorType.a; ++ }); ++ public static final IRegistry SCHEDULE = a(IRegistry.F, () -> { ++ return Schedule.EMPTY; ++ }); ++ public static final IRegistry ACTIVITY = a(IRegistry.G, () -> { ++ return Activity.IDLE; ++ }); ++ public static final IRegistry ao = a(IRegistry.H, () -> { ++ return LootEntries.a; ++ }); ++ public static final IRegistry ap = a(IRegistry.I, () -> { ++ return LootItemFunctions.b; ++ }); ++ public static final IRegistry aq = a(IRegistry.J, () -> { ++ return LootItemConditions.a; ++ }); ++ public static final ResourceKey> ar = a("worldgen/noise_settings"); ++ public static final ResourceKey>> as = a("worldgen/configured_surface_builder"); ++ public static final ResourceKey>> at = a("worldgen/configured_carver"); ++ public static final ResourceKey>> au = a("worldgen/configured_feature"); ++ public static final ResourceKey>> av = a("worldgen/configured_structure_feature"); ++ public static final ResourceKey> aw = a("worldgen/processor_list"); ++ public static final ResourceKey> ax = a("worldgen/template_pool"); ++ public static final ResourceKey> ay = a("worldgen/biome"); ++ public static final ResourceKey>> az = a("worldgen/surface_builder"); ++ public static final IRegistry> SURFACE_BUILDER = a(IRegistry.az, () -> { ++ return WorldGenSurface.v; ++ }); ++ public static final ResourceKey>> aB = a("worldgen/carver"); ++ public static final IRegistry> CARVER = a(IRegistry.aB, () -> { ++ return WorldGenCarverAbstract.a; ++ }); ++ public static final ResourceKey>> aD = a("worldgen/feature"); ++ public static final IRegistry> FEATURE = a(IRegistry.aD, () -> { ++ return WorldGenerator.ORE; ++ }); ++ public static final ResourceKey>> aF = a("worldgen/structure_feature"); ++ public static final IRegistry> STRUCTURE_FEATURE = a(IRegistry.aF, () -> { ++ return StructureGenerator.MINESHAFT; ++ }); ++ public static final ResourceKey> aH = a("worldgen/structure_piece"); ++ public static final IRegistry STRUCTURE_PIECE = a(IRegistry.aH, () -> { ++ return WorldGenFeatureStructurePieceType.c; ++ }); ++ public static final ResourceKey>> aJ = a("worldgen/decorator"); ++ public static final IRegistry> DECORATOR = a(IRegistry.aJ, () -> { ++ return WorldGenDecorator.a; ++ }); ++ public static final ResourceKey>> aL = a("worldgen/block_state_provider_type"); ++ public static final ResourceKey>> aM = a("worldgen/block_placer_type"); ++ public static final ResourceKey>> aN = a("worldgen/foliage_placer_type"); ++ public static final ResourceKey>> aO = a("worldgen/trunk_placer_type"); ++ public static final ResourceKey>> aP = a("worldgen/tree_decorator_type"); ++ public static final ResourceKey>> aQ = a("worldgen/feature_size_type"); ++ public static final ResourceKey>> aR = a("worldgen/biome_source"); ++ public static final ResourceKey>> aS = a("worldgen/chunk_generator"); ++ public static final ResourceKey>> aT = a("worldgen/structure_processor"); ++ public static final ResourceKey>> aU = a("worldgen/structure_pool_element"); ++ public static final IRegistry> BLOCK_STATE_PROVIDER_TYPE = a(IRegistry.aL, () -> { ++ return WorldGenFeatureStateProviders.a; ++ }); ++ public static final IRegistry> BLOCK_PLACER_TYPE = a(IRegistry.aM, () -> { ++ return WorldGenBlockPlacers.a; ++ }); ++ public static final IRegistry> FOLIAGE_PLACER_TYPE = a(IRegistry.aN, () -> { ++ return WorldGenFoilagePlacers.a; ++ }); ++ public static final IRegistry> TRUNK_PLACER_TYPE = a(IRegistry.aO, () -> { ++ return TrunkPlacers.a; ++ }); ++ public static final IRegistry> TREE_DECORATOR_TYPE = a(IRegistry.aP, () -> { ++ return WorldGenFeatureTrees.b; ++ }); ++ public static final IRegistry> FEATURE_SIZE_TYPE = a(IRegistry.aQ, () -> { ++ return FeatureSizeType.a; ++ }); ++ public static final IRegistry> BIOME_SOURCE = a(IRegistry.aR, Lifecycle.stable(), () -> { ++ return WorldChunkManager.a; ++ }); ++ public static final IRegistry> CHUNK_GENERATOR = a(IRegistry.aS, Lifecycle.stable(), () -> { ++ return ChunkGenerator.a; ++ }); ++ public static final IRegistry> STRUCTURE_PROCESSOR = a(IRegistry.aT, () -> { ++ return DefinedStructureStructureProcessorType.a; ++ }); ++ public static final IRegistry> STRUCTURE_POOL_ELEMENT = a(IRegistry.aU, () -> { ++ return WorldGenFeatureDefinedStructurePools.d; ++ }); ++ private final ResourceKey> b; ++ private final Lifecycle bf; ++ ++ private static ResourceKey> a(String s) { ++ return ResourceKey.a(new MinecraftKey(s)); ++ } ++ ++ public static > void a(IRegistryWritable iregistrywritable) { ++ iregistrywritable.forEach((iregistrywritable1) -> { ++ if (iregistrywritable1.keySet().isEmpty()) { ++ IRegistry.LOGGER.error("Registry '{}' was empty after loading", iregistrywritable.getKey(iregistrywritable1)); ++ if (SharedConstants.d) { ++ throw new IllegalStateException("Registry: '" + iregistrywritable.getKey(iregistrywritable1) + "' is empty, not allowed, fix me!"); ++ } ++ } ++ ++ if (iregistrywritable1 instanceof RegistryBlocks) { ++ MinecraftKey minecraftkey = ((RegistryBlocks) iregistrywritable1).a(); ++ ++ Validate.notNull(iregistrywritable1.get(minecraftkey), "Missing default of DefaultedMappedRegistry: " + minecraftkey, new Object[0]); ++ } ++ ++ }); ++ } ++ ++ private static IRegistry a(ResourceKey> resourcekey, Supplier supplier) { ++ return a(resourcekey, Lifecycle.experimental(), supplier); ++ } ++ ++ private static RegistryBlocks a(ResourceKey> resourcekey, String s, Supplier supplier) { ++ return a(resourcekey, s, Lifecycle.experimental(), supplier); ++ } ++ ++ private static IRegistry a(ResourceKey> resourcekey, Lifecycle lifecycle, Supplier supplier) { ++ return a(resourcekey, (IRegistryWritable) (new RegistryMaterials<>(resourcekey, lifecycle)), supplier, lifecycle); ++ } ++ ++ private static RegistryBlocks a(ResourceKey> resourcekey, String s, Lifecycle lifecycle, Supplier supplier) { ++ return (RegistryBlocks) a(resourcekey, (IRegistryWritable) (new RegistryBlocks<>(s, resourcekey, lifecycle)), supplier, lifecycle); ++ } ++ ++ private static > R a(ResourceKey> resourcekey, R r0, Supplier supplier, Lifecycle lifecycle) { ++ MinecraftKey minecraftkey = resourcekey.a(); ++ ++ IRegistry.a.put(minecraftkey, supplier); ++ IRegistryWritable iregistrywritable = IRegistry.e; ++ ++ return (IRegistryWritable) iregistrywritable.a(resourcekey, (Object) r0, lifecycle); ++ } ++ ++ protected IRegistry(ResourceKey> resourcekey, Lifecycle lifecycle) { ++ this.b = resourcekey; ++ this.bf = lifecycle; ++ } ++ ++ public ResourceKey> f() { ++ return this.b; ++ } ++ ++ public String toString() { ++ return "Registry[" + this.b + " (" + this.bf + ")]"; ++ } ++ ++ public DataResult> decode(DynamicOps dynamicops, U u0) { ++ return dynamicops.compressMaps() ? dynamicops.getNumberValue(u0).flatMap((number) -> { ++ T t0 = this.fromId(number.intValue()); ++ ++ return t0 == null ? DataResult.error("Unknown registry id: " + number) : DataResult.success(t0, this.d(t0)); ++ }).map((object) -> { ++ return Pair.of(object, dynamicops.empty()); ++ }) : MinecraftKey.a.decode(dynamicops, u0).flatMap((pair) -> { ++ T t0 = this.get((MinecraftKey) pair.getFirst()); ++ ++ return t0 == null ? DataResult.error("Unknown registry key: " + pair.getFirst()) : DataResult.success(Pair.of(t0, pair.getSecond()), this.d(t0)); ++ }); ++ } ++ ++ public DataResult encode(T t0, DynamicOps dynamicops, U u0) { ++ MinecraftKey minecraftkey = this.getKey(t0); ++ ++ return minecraftkey == null ? DataResult.error("Unknown registry element " + t0) : (dynamicops.compressMaps() ? dynamicops.mergeToPrimitive(u0, dynamicops.createInt(this.a(t0))).setLifecycle(this.bf) : dynamicops.mergeToPrimitive(u0, dynamicops.createString(minecraftkey.toString())).setLifecycle(this.bf)); ++ } ++ ++ public Stream keys(DynamicOps dynamicops) { ++ return this.keySet().stream().map((minecraftkey) -> { ++ return dynamicops.createString(minecraftkey.toString()); ++ }); ++ } ++ ++ @Nullable ++ public abstract MinecraftKey getKey(T t0); ++ ++ public abstract Optional> c(T t0); ++ ++ @Override ++ public abstract int a(@Nullable T t0); ++ ++ @Nullable ++ public abstract T a(@Nullable ResourceKey resourcekey); ++ ++ @Nullable ++ public abstract T get(@Nullable MinecraftKey minecraftkey); ++ ++ protected abstract Lifecycle d(T t0); ++ ++ public abstract Lifecycle b(); ++ ++ public Optional getOptional(@Nullable MinecraftKey minecraftkey) { ++ return Optional.ofNullable(this.get(minecraftkey)); ++ } ++ ++ public T d(ResourceKey resourcekey) { ++ T t0 = this.a(resourcekey); ++ ++ if (t0 == null) { ++ throw new IllegalStateException("Missing: " + resourcekey); ++ } else { ++ return t0; ++ } ++ } ++ ++ public abstract Set keySet(); ++ ++ public abstract Set, T>> d(); ++ ++ public Stream g() { ++ return StreamSupport.stream(this.spliterator(), false); ++ } ++ ++ public static T a(IRegistry iregistry, String s, T t0) { ++ return a(iregistry, new MinecraftKey(s), t0); ++ } ++ ++ public static T a(IRegistry iregistry, MinecraftKey minecraftkey, T t0) { ++ return ((IRegistryWritable) iregistry).a(ResourceKey.a(iregistry.b, minecraftkey), t0, Lifecycle.stable()); ++ } ++ ++ public static T a(IRegistry iregistry, int i, String s, T t0) { ++ return ((IRegistryWritable) iregistry).a(i, ResourceKey.a(iregistry.b, new MinecraftKey(s)), t0, Lifecycle.stable()); ++ } ++ ++ static { ++ RegistryGeneration.a(); ++ IRegistry.a.forEach((minecraftkey, supplier) -> { ++ if (supplier.get() == null) { ++ IRegistry.LOGGER.error("Unable to bootstrap registry '{}'", minecraftkey); ++ } ++ ++ }); ++ a(IRegistry.e); ++ } ++} +diff --git a/src/main/java/net/minecraft/core/RegistryBlockID.java b/src/main/java/net/minecraft/core/RegistryBlockID.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b173ed8a6abeee41ce48e03f6403f2eb4978155b +--- /dev/null ++++ b/src/main/java/net/minecraft/core/RegistryBlockID.java +@@ -0,0 +1,63 @@ ++package net.minecraft.core; ++ ++import com.google.common.base.Predicates; ++import com.google.common.collect.Iterators; ++import com.google.common.collect.Lists; ++import java.util.IdentityHashMap; ++import java.util.Iterator; ++import java.util.List; ++import javax.annotation.Nullable; ++ ++public class RegistryBlockID implements Registry { ++ ++ private int a; ++ private final IdentityHashMap b; ++ private final List c; ++ ++ public RegistryBlockID() { ++ this(512); ++ } ++ ++ public RegistryBlockID(int i) { ++ this.c = Lists.newArrayListWithExpectedSize(i); ++ this.b = new IdentityHashMap(i); ++ } ++ ++ public void a(T t0, int i) { ++ this.b.put(t0, i); ++ ++ while (this.c.size() <= i) { ++ this.c.add((Object) null); ++ } ++ ++ this.c.set(i, t0); ++ if (this.a <= i) { ++ this.a = i + 1; ++ } ++ ++ } ++ ++ public void b(T t0) { ++ this.a(t0, this.a); ++ } ++ ++ public int getId(T t0) { ++ Integer integer = (Integer) this.b.get(t0); ++ ++ return integer == null ? -1 : integer; ++ } ++ ++ @Nullable ++ @Override ++ public final T fromId(int i) { ++ return i >= 0 && i < this.c.size() ? this.c.get(i) : null; ++ } ++ ++ public Iterator iterator() { ++ return Iterators.filter(this.c.iterator(), Predicates.notNull()); ++ } ++ ++ public int a() { ++ return this.b.size(); ++ } ++} +diff --git a/src/main/java/net/minecraft/core/RegistryMaterials.java b/src/main/java/net/minecraft/core/RegistryMaterials.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f3f6ed83d509d228944d15fc2b2b4cb85b05e366 +--- /dev/null ++++ b/src/main/java/net/minecraft/core/RegistryMaterials.java +@@ -0,0 +1,256 @@ ++package net.minecraft.core; ++ ++import com.google.common.collect.BiMap; ++import com.google.common.collect.HashBiMap; ++import com.google.common.collect.ImmutableList; ++import com.google.common.collect.ImmutableList.Builder; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Iterators; ++import com.google.common.collect.Maps; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.Lifecycle; ++import com.mojang.serialization.MapCodec; ++import com.mojang.serialization.codecs.RecordCodecBuilder; ++import it.unimi.dsi.fastutil.objects.Object2IntMap; ++import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; ++import it.unimi.dsi.fastutil.objects.ObjectList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Map.Entry; ++import java.util.Objects; ++import java.util.Optional; ++import java.util.OptionalInt; ++import java.util.Random; ++import java.util.Set; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.resources.RegistryDataPackCodec; ++import net.minecraft.resources.ResourceKey; ++import org.apache.commons.lang3.Validate; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class RegistryMaterials extends IRegistryWritable { ++ ++ protected static final Logger LOGGER = LogManager.getLogger(); ++ private final ObjectList bf = new ObjectArrayList(256); ++ private final Object2IntMap bg = new Object2IntOpenCustomHashMap(SystemUtils.k()); ++ private final BiMap bh; ++ private final BiMap, T> bi; ++ private final Map bj; ++ private Lifecycle bk; ++ protected Object[] b; ++ private int bl; ++ ++ public RegistryMaterials(ResourceKey> resourcekey, Lifecycle lifecycle) { ++ super(resourcekey, lifecycle); ++ this.bg.defaultReturnValue(-1); ++ this.bh = HashBiMap.create(); ++ this.bi = HashBiMap.create(); ++ this.bj = Maps.newIdentityHashMap(); ++ this.bk = lifecycle; ++ } ++ ++ public static MapCodec> a(ResourceKey> resourcekey, MapCodec mapcodec) { ++ return RecordCodecBuilder.mapCodec((instance) -> { ++ return instance.group(MinecraftKey.a.xmap(ResourceKey.b(resourcekey), ResourceKey::a).fieldOf("name").forGetter((registrymaterials_a) -> { ++ return registrymaterials_a.a; ++ }), Codec.INT.fieldOf("id").forGetter((registrymaterials_a) -> { ++ return registrymaterials_a.b; ++ }), mapcodec.forGetter((registrymaterials_a) -> { ++ return registrymaterials_a.c; ++ })).apply(instance, RegistryMaterials.a::new); ++ }); ++ } ++ ++ @Override ++ public V a(int i, ResourceKey resourcekey, V v0, Lifecycle lifecycle) { ++ return this.a(i, resourcekey, v0, lifecycle, true); ++ } ++ ++ private V a(int i, ResourceKey resourcekey, V v0, Lifecycle lifecycle, boolean flag) { ++ Validate.notNull(resourcekey); ++ Validate.notNull(v0); ++ this.bf.size(Math.max(this.bf.size(), i + 1)); ++ this.bf.set(i, v0); ++ this.bg.put(v0, i); ++ this.b = null; ++ if (flag && this.bi.containsKey(resourcekey)) { ++ RegistryMaterials.LOGGER.debug("Adding duplicate key '{}' to registry", resourcekey); ++ } ++ ++ if (this.bh.containsValue(v0)) { ++ RegistryMaterials.LOGGER.error("Adding duplicate value '{}' to registry", v0); ++ } ++ ++ this.bh.put(resourcekey.a(), v0); ++ this.bi.put(resourcekey, v0); ++ this.bj.put(v0, lifecycle); ++ this.bk = this.bk.add(lifecycle); ++ if (this.bl <= i) { ++ this.bl = i + 1; ++ } ++ ++ return v0; ++ } ++ ++ @Override ++ public V a(ResourceKey resourcekey, V v0, Lifecycle lifecycle) { ++ return this.a(this.bl, resourcekey, v0, lifecycle); ++ } ++ ++ @Override ++ public V a(OptionalInt optionalint, ResourceKey resourcekey, V v0, Lifecycle lifecycle) { ++ Validate.notNull(resourcekey); ++ Validate.notNull(v0); ++ T t0 = this.bi.get(resourcekey); ++ int i; ++ ++ if (t0 == null) { ++ i = optionalint.isPresent() ? optionalint.getAsInt() : this.bl; ++ } else { ++ i = this.bg.getInt(t0); ++ if (optionalint.isPresent() && optionalint.getAsInt() != i) { ++ throw new IllegalStateException("ID mismatch"); ++ } ++ ++ this.bg.removeInt(t0); ++ this.bj.remove(t0); ++ } ++ ++ return this.a(i, resourcekey, v0, lifecycle, false); ++ } ++ ++ @Nullable ++ @Override ++ public MinecraftKey getKey(T t0) { ++ return (MinecraftKey) this.bh.inverse().get(t0); ++ } ++ ++ @Override ++ public Optional> c(T t0) { ++ return Optional.ofNullable(this.bi.inverse().get(t0)); ++ } ++ ++ @Override ++ public int a(@Nullable T t0) { ++ return this.bg.getInt(t0); ++ } ++ ++ @Nullable ++ @Override ++ public T a(@Nullable ResourceKey resourcekey) { ++ return this.bi.get(resourcekey); ++ } ++ ++ @Nullable ++ @Override ++ public T fromId(int i) { ++ return i >= 0 && i < this.bf.size() ? this.bf.get(i) : null; ++ } ++ ++ @Override ++ public Lifecycle d(T t0) { ++ return (Lifecycle) this.bj.get(t0); ++ } ++ ++ @Override ++ public Lifecycle b() { ++ return this.bk; ++ } ++ ++ public Iterator iterator() { ++ return Iterators.filter(this.bf.iterator(), Objects::nonNull); ++ } ++ ++ @Nullable ++ @Override ++ public T get(@Nullable MinecraftKey minecraftkey) { ++ return this.bh.get(minecraftkey); ++ } ++ ++ @Override ++ public Set keySet() { ++ return Collections.unmodifiableSet(this.bh.keySet()); ++ } ++ ++ @Override ++ public Set, T>> d() { ++ return Collections.unmodifiableMap(this.bi).entrySet(); ++ } ++ ++ @Nullable ++ public T a(Random random) { ++ if (this.b == null) { ++ Collection collection = this.bh.values(); ++ ++ if (collection.isEmpty()) { ++ return null; ++ } ++ ++ this.b = collection.toArray(new Object[collection.size()]); ++ } ++ ++ return SystemUtils.a(this.b, random); ++ } ++ ++ public static Codec> a(ResourceKey> resourcekey, Lifecycle lifecycle, Codec codec) { ++ return a(resourcekey, codec.fieldOf("element")).codec().listOf().xmap((list) -> { ++ RegistryMaterials registrymaterials = new RegistryMaterials<>(resourcekey, lifecycle); ++ Iterator iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ RegistryMaterials.a registrymaterials_a = (RegistryMaterials.a) iterator.next(); ++ ++ registrymaterials.a(registrymaterials_a.b, registrymaterials_a.a, registrymaterials_a.c, lifecycle); ++ } ++ ++ return registrymaterials; ++ }, (registrymaterials) -> { ++ Builder> builder = ImmutableList.builder(); ++ Iterator iterator = registrymaterials.iterator(); ++ ++ while (iterator.hasNext()) { ++ T t0 = iterator.next(); ++ ++ builder.add(new RegistryMaterials.a<>((ResourceKey) registrymaterials.c(t0).get(), registrymaterials.a(t0), t0)); ++ } ++ ++ return builder.build(); ++ }); ++ } ++ ++ public static Codec> b(ResourceKey> resourcekey, Lifecycle lifecycle, Codec codec) { ++ return RegistryDataPackCodec.a(resourcekey, lifecycle, codec); ++ } ++ ++ public static Codec> c(ResourceKey> resourcekey, Lifecycle lifecycle, Codec codec) { ++ return Codec.unboundedMap(MinecraftKey.a.xmap(ResourceKey.b(resourcekey), ResourceKey::a), codec).xmap((map) -> { ++ RegistryMaterials registrymaterials = new RegistryMaterials<>(resourcekey, lifecycle); ++ ++ map.forEach((resourcekey1, object) -> { ++ registrymaterials.a(resourcekey1, object, lifecycle); ++ }); ++ return registrymaterials; ++ }, (registrymaterials) -> { ++ return ImmutableMap.copyOf(registrymaterials.bi); ++ }); ++ } ++ ++ public static class a { ++ ++ public final ResourceKey a; ++ public final int b; ++ public final T c; ++ ++ public a(ResourceKey resourcekey, int i, T t0) { ++ this.a = resourcekey; ++ this.b = i; ++ this.c = t0; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/core/SectionPosition.java b/src/main/java/net/minecraft/core/SectionPosition.java +new file mode 100644 +index 0000000000000000000000000000000000000000..97126ae5a43bb7acb04a1ab14fb7f364c8c2675f +--- /dev/null ++++ b/src/main/java/net/minecraft/core/SectionPosition.java +@@ -0,0 +1,209 @@ ++package net.minecraft.core; ++ ++import java.util.Spliterators.AbstractSpliterator; ++import java.util.function.Consumer; ++import java.util.stream.Stream; ++import java.util.stream.StreamSupport; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkCoordIntPair; ++ ++public class SectionPosition extends BaseBlockPosition { ++ ++ private SectionPosition(int i, int j, int k) { ++ super(i, j, k); ++ } ++ ++ public static SectionPosition a(int i, int j, int k) { ++ return new SectionPosition(i, j, k); ++ } ++ ++ public static SectionPosition a(BlockPosition blockposition) { ++ return new SectionPosition(a(blockposition.getX()), a(blockposition.getY()), a(blockposition.getZ())); ++ } ++ ++ public static SectionPosition a(ChunkCoordIntPair chunkcoordintpair, int i) { ++ return new SectionPosition(chunkcoordintpair.x, i, chunkcoordintpair.z); ++ } ++ ++ public static SectionPosition a(Entity entity) { ++ return new SectionPosition(a(MathHelper.floor(entity.locX())), a(MathHelper.floor(entity.locY())), a(MathHelper.floor(entity.locZ()))); ++ } ++ ++ public static SectionPosition a(long i) { ++ return new SectionPosition(b(i), c(i), d(i)); ++ } ++ ++ public static long a(long i, EnumDirection enumdirection) { ++ return a(i, enumdirection.getAdjacentX(), enumdirection.getAdjacentY(), enumdirection.getAdjacentZ()); ++ } ++ ++ public static long a(long i, int j, int k, int l) { ++ return b(b(i) + j, c(i) + k, d(i) + l); ++ } ++ ++ public static int a(int i) { ++ return i >> 4; ++ } ++ ++ public static int b(int i) { ++ return i & 15; ++ } ++ ++ public static short b(BlockPosition blockposition) { ++ int i = b(blockposition.getX()); ++ int j = b(blockposition.getY()); ++ int k = b(blockposition.getZ()); ++ ++ return (short) (i << 8 | k << 4 | j << 0); ++ } ++ ++ public static int a(short short0) { ++ return short0 >>> 8 & 15; ++ } ++ ++ public static int b(short short0) { ++ return short0 >>> 0 & 15; ++ } ++ ++ public static int c(short short0) { ++ return short0 >>> 4 & 15; ++ } ++ ++ public int d(short short0) { ++ return this.d() + a(short0); ++ } ++ ++ public int e(short short0) { ++ return this.e() + b(short0); ++ } ++ ++ public int f(short short0) { ++ return this.f() + c(short0); ++ } ++ ++ public BlockPosition g(short short0) { ++ return new BlockPosition(this.d(short0), this.e(short0), this.f(short0)); ++ } ++ ++ public static int c(int i) { ++ return i << 4; ++ } ++ ++ public static int b(long i) { ++ return (int) (i << 0 >> 42); ++ } ++ ++ public static int c(long i) { ++ return (int) (i << 44 >> 44); ++ } ++ ++ public static int d(long i) { ++ return (int) (i << 22 >> 42); ++ } ++ ++ public int a() { ++ return this.getX(); ++ } ++ ++ public int b() { ++ return this.getY(); ++ } ++ ++ public int c() { ++ return this.getZ(); ++ } ++ ++ public int d() { ++ return this.a() << 4; ++ } ++ ++ public int e() { ++ return this.b() << 4; ++ } ++ ++ public int f() { ++ return this.c() << 4; ++ } ++ ++ public int g() { ++ return (this.a() << 4) + 15; ++ } ++ ++ public int h() { ++ return (this.b() << 4) + 15; ++ } ++ ++ public int i() { ++ return (this.c() << 4) + 15; ++ } ++ ++ public static long e(long i) { ++ return b(a(BlockPosition.b(i)), a(BlockPosition.c(i)), a(BlockPosition.d(i))); ++ } ++ ++ public static long f(long i) { ++ return i & -1048576L; ++ } ++ ++ public BlockPosition p() { ++ return new BlockPosition(c(this.a()), c(this.b()), c(this.c())); ++ } ++ ++ public BlockPosition q() { ++ boolean flag = true; ++ ++ return this.p().b(8, 8, 8); ++ } ++ ++ public ChunkCoordIntPair r() { ++ return new ChunkCoordIntPair(this.a(), this.c()); ++ } ++ ++ public static long b(int i, int j, int k) { ++ long l = 0L; ++ ++ l |= ((long) i & 4194303L) << 42; ++ l |= ((long) j & 1048575L) << 0; ++ l |= ((long) k & 4194303L) << 20; ++ return l; ++ } ++ ++ public long s() { ++ return b(this.a(), this.b(), this.c()); ++ } ++ ++ public Stream t() { ++ return BlockPosition.a(this.d(), this.e(), this.f(), this.g(), this.h(), this.i()); ++ } ++ ++ public static Stream a(SectionPosition sectionposition, int i) { ++ int j = sectionposition.a(); ++ int k = sectionposition.b(); ++ int l = sectionposition.c(); ++ ++ return a(j - i, k - i, l - i, j + i, k + i, l + i); ++ } ++ ++ public static Stream b(ChunkCoordIntPair chunkcoordintpair, int i) { ++ int j = chunkcoordintpair.x; ++ int k = chunkcoordintpair.z; ++ ++ return a(j - i, 0, k - i, j + i, 15, k + i); ++ } ++ ++ public static Stream a(final int i, final int j, final int k, final int l, final int i1, final int j1) { ++ return StreamSupport.stream(new AbstractSpliterator((long) ((l - i + 1) * (i1 - j + 1) * (j1 - k + 1)), 64) { ++ final CursorPosition a = new CursorPosition(i, j, k, l, i1, j1); ++ ++ public boolean tryAdvance(Consumer consumer) { ++ if (this.a.a()) { ++ consumer.accept(new SectionPosition(this.a.b(), this.a.c(), this.a.d())); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ }, false); ++ } ++} +diff --git a/src/main/java/net/minecraft/core/Vector3f.java b/src/main/java/net/minecraft/core/Vector3f.java +new file mode 100644 +index 0000000000000000000000000000000000000000..93590ceb0bbe369a1bda987f0c4c21ea6a3b3a1a +--- /dev/null ++++ b/src/main/java/net/minecraft/core/Vector3f.java +@@ -0,0 +1,52 @@ ++package net.minecraft.core; ++ ++import net.minecraft.nbt.NBTTagFloat; ++import net.minecraft.nbt.NBTTagList; ++ ++public class Vector3f { ++ ++ protected final float x; ++ protected final float y; ++ protected final float z; ++ ++ public Vector3f(float f, float f1, float f2) { ++ this.x = !Float.isInfinite(f) && !Float.isNaN(f) ? f % 360.0F : 0.0F; ++ this.y = !Float.isInfinite(f1) && !Float.isNaN(f1) ? f1 % 360.0F : 0.0F; ++ this.z = !Float.isInfinite(f2) && !Float.isNaN(f2) ? f2 % 360.0F : 0.0F; ++ } ++ ++ public Vector3f(NBTTagList nbttaglist) { ++ this(nbttaglist.i(0), nbttaglist.i(1), nbttaglist.i(2)); ++ } ++ ++ public NBTTagList a() { ++ NBTTagList nbttaglist = new NBTTagList(); ++ ++ nbttaglist.add(NBTTagFloat.a(this.x)); ++ nbttaglist.add(NBTTagFloat.a(this.y)); ++ nbttaglist.add(NBTTagFloat.a(this.z)); ++ return nbttaglist; ++ } ++ ++ public boolean equals(Object object) { ++ if (!(object instanceof Vector3f)) { ++ return false; ++ } else { ++ Vector3f vector3f = (Vector3f) object; ++ ++ return this.x == vector3f.x && this.y == vector3f.y && this.z == vector3f.z; ++ } ++ } ++ ++ public float getX() { ++ return this.x; ++ } ++ ++ public float getY() { ++ return this.y; ++ } ++ ++ public float getZ() { ++ return this.z; ++ } ++} +diff --git a/src/main/java/net/minecraft/data/RegistryGeneration.java b/src/main/java/net/minecraft/data/RegistryGeneration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..25762daca063ff5a422975d7fe64752a2deae163 +--- /dev/null ++++ b/src/main/java/net/minecraft/data/RegistryGeneration.java +@@ -0,0 +1,97 @@ ++package net.minecraft.data; ++ ++import com.google.common.collect.Maps; ++import com.mojang.serialization.Lifecycle; ++import java.util.Map; ++import java.util.function.Supplier; ++import net.minecraft.core.IRegistry; ++import net.minecraft.core.IRegistryWritable; ++import net.minecraft.core.RegistryMaterials; ++import net.minecraft.data.worldgen.BiomeDecoratorGroups; ++import net.minecraft.data.worldgen.ProcessorLists; ++import net.minecraft.data.worldgen.StructureFeatures; ++import net.minecraft.data.worldgen.WorldGenCarvers; ++import net.minecraft.data.worldgen.WorldGenFeaturePieces; ++import net.minecraft.data.worldgen.WorldGenSurfaceComposites; ++import net.minecraft.data.worldgen.biome.BiomeRegistry; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.world.level.biome.BiomeBase; ++import net.minecraft.world.level.levelgen.GeneratorSettingBase; ++import net.minecraft.world.level.levelgen.carver.WorldGenCarverWrapper; ++import net.minecraft.world.level.levelgen.feature.StructureFeature; ++import net.minecraft.world.level.levelgen.feature.WorldGenFeatureConfigured; ++import net.minecraft.world.level.levelgen.feature.structures.WorldGenFeatureDefinedStructurePoolTemplate; ++import net.minecraft.world.level.levelgen.structure.templatesystem.ProcessorList; ++import net.minecraft.world.level.levelgen.surfacebuilders.WorldGenSurfaceComposite; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class RegistryGeneration { ++ ++ protected static final Logger LOGGER = LogManager.getLogger(); ++ private static final Map> k = Maps.newLinkedHashMap(); ++ private static final IRegistryWritable> l = new RegistryMaterials<>(ResourceKey.a(new MinecraftKey("root")), Lifecycle.experimental()); ++ public static final IRegistry> b = RegistryGeneration.l; ++ public static final IRegistry> c = a(IRegistry.as, () -> { ++ return WorldGenSurfaceComposites.p; ++ }); ++ public static final IRegistry> d = a(IRegistry.at, () -> { ++ return WorldGenCarvers.a; ++ }); ++ public static final IRegistry> e = a(IRegistry.au, () -> { ++ return BiomeDecoratorGroups.OAK; ++ }); ++ public static final IRegistry> f = a(IRegistry.av, () -> { ++ return StructureFeatures.b; ++ }); ++ public static final IRegistry g = a(IRegistry.aw, () -> { ++ return ProcessorLists.b; ++ }); ++ public static final IRegistry h = a(IRegistry.ax, WorldGenFeaturePieces::a); ++ public static final IRegistry WORLDGEN_BIOME = a(IRegistry.ay, () -> { ++ return BiomeRegistry.a; ++ }); ++ public static final IRegistry j = a(IRegistry.ar, GeneratorSettingBase::i); ++ ++ private static IRegistry a(ResourceKey> resourcekey, Supplier supplier) { ++ return a(resourcekey, Lifecycle.stable(), supplier); ++ } ++ ++ private static IRegistry a(ResourceKey> resourcekey, Lifecycle lifecycle, Supplier supplier) { ++ return a(resourcekey, new RegistryMaterials<>(resourcekey, lifecycle), supplier, lifecycle); ++ } ++ ++ private static > R a(ResourceKey> resourcekey, R r0, Supplier supplier, Lifecycle lifecycle) { ++ MinecraftKey minecraftkey = resourcekey.a(); ++ ++ RegistryGeneration.k.put(minecraftkey, supplier); ++ IRegistryWritable iregistrywritable = RegistryGeneration.l; ++ ++ return (IRegistryWritable) iregistrywritable.a(resourcekey, (Object) r0, lifecycle); ++ } ++ ++ public static T a(IRegistry iregistry, String s, T t0) { ++ return a(iregistry, new MinecraftKey(s), t0); ++ } ++ ++ public static T a(IRegistry iregistry, MinecraftKey minecraftkey, T t0) { ++ return ((IRegistryWritable) iregistry).a(ResourceKey.a(iregistry.f(), minecraftkey), t0, Lifecycle.stable()); ++ } ++ ++ public static T a(IRegistry iregistry, int i, ResourceKey resourcekey, T t0) { ++ return ((IRegistryWritable) iregistry).a(i, resourcekey, t0, Lifecycle.stable()); ++ } ++ ++ public static void a() {} ++ ++ static { ++ RegistryGeneration.k.forEach((minecraftkey, supplier) -> { ++ if (supplier.get() == null) { ++ RegistryGeneration.LOGGER.error("Unable to bootstrap registry '{}'", minecraftkey); ++ } ++ ++ }); ++ IRegistry.a(RegistryGeneration.l); ++ } ++} +diff --git a/src/main/java/net/minecraft/locale/LocaleLanguage.java b/src/main/java/net/minecraft/locale/LocaleLanguage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9b8d5e7e4c86a699e26b1b4d0b82e88887a44054 +--- /dev/null ++++ b/src/main/java/net/minecraft/locale/LocaleLanguage.java +@@ -0,0 +1,97 @@ ++package net.minecraft.locale; ++ ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.ImmutableMap.Builder; ++import com.google.gson.Gson; ++import com.google.gson.JsonElement; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonParseException; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.InputStreamReader; ++import java.nio.charset.StandardCharsets; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Map.Entry; ++import java.util.function.BiConsumer; ++import java.util.regex.Pattern; ++import net.minecraft.util.ChatDeserializer; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public abstract class LocaleLanguage { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private static final Gson b = new Gson(); ++ private static final Pattern c = Pattern.compile("%(\\d+\\$)?[\\d.]*[df]"); ++ private static volatile LocaleLanguage d = c(); ++ ++ public LocaleLanguage() {} ++ ++ private static LocaleLanguage c() { ++ Builder builder = ImmutableMap.builder(); ++ BiConsumer biconsumer = builder::put; ++ ++ try { ++ InputStream inputstream = LocaleLanguage.class.getResourceAsStream("/assets/minecraft/lang/en_us.json"); ++ Throwable throwable = null; ++ ++ try { ++ a(inputstream, biconsumer); ++ } catch (Throwable throwable1) { ++ throwable = throwable1; ++ throw throwable1; ++ } finally { ++ if (inputstream != null) { ++ if (throwable != null) { ++ try { ++ inputstream.close(); ++ } catch (Throwable throwable2) { ++ throwable.addSuppressed(throwable2); ++ } ++ } else { ++ inputstream.close(); ++ } ++ } ++ ++ } ++ } catch (JsonParseException | IOException ioexception) { ++ LocaleLanguage.LOGGER.error("Couldn't read strings from /assets/minecraft/lang/en_us.json", ioexception); ++ } ++ ++ final Map map = builder.build(); ++ ++ return new LocaleLanguage() { ++ @Override ++ public String a(String s) { ++ return (String) map.getOrDefault(s, s); ++ } ++ ++ @Override ++ public boolean b(String s) { ++ return map.containsKey(s); ++ } ++ }; ++ } ++ ++ public static void a(InputStream inputstream, BiConsumer biconsumer) { ++ JsonObject jsonobject = (JsonObject) LocaleLanguage.b.fromJson(new InputStreamReader(inputstream, StandardCharsets.UTF_8), JsonObject.class); ++ Iterator iterator = jsonobject.entrySet().iterator(); ++ ++ while (iterator.hasNext()) { ++ Entry entry = (Entry) iterator.next(); ++ String s = LocaleLanguage.c.matcher(ChatDeserializer.a((JsonElement) entry.getValue(), (String) entry.getKey())).replaceAll("%$1s"); ++ ++ biconsumer.accept(entry.getKey(), s); ++ } ++ ++ } ++ ++ public static LocaleLanguage a() { ++ return LocaleLanguage.d; ++ } ++ ++ public abstract String a(String s); ++ ++ public abstract boolean b(String s); ++} +diff --git a/src/main/java/net/minecraft/nbt/GameProfileSerializer.java b/src/main/java/net/minecraft/nbt/GameProfileSerializer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0560d115288c67e46d921ce529e603f424e601f5 +--- /dev/null ++++ b/src/main/java/net/minecraft/nbt/GameProfileSerializer.java +@@ -0,0 +1,286 @@ ++package net.minecraft.nbt; ++ ++import com.google.common.annotations.VisibleForTesting; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.UnmodifiableIterator; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import com.mojang.datafixers.DataFixer; ++import com.mojang.serialization.Dynamic; ++import java.util.Iterator; ++import java.util.Map.Entry; ++import java.util.Optional; ++import java.util.UUID; ++import javax.annotation.Nullable; ++import net.minecraft.SharedConstants; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.IRegistry; ++import net.minecraft.core.MinecraftSerializableUUID; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.util.UtilColor; ++import net.minecraft.util.datafix.DataFixTypes; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockStateList; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.block.state.IBlockDataHolder; ++import net.minecraft.world.level.block.state.properties.IBlockState; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public final class GameProfileSerializer { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ ++ @Nullable ++ public static GameProfile deserialize(NBTTagCompound nbttagcompound) { ++ String s = null; ++ UUID uuid = null; ++ ++ if (nbttagcompound.hasKeyOfType("Name", 8)) { ++ s = nbttagcompound.getString("Name"); ++ } ++ ++ if (nbttagcompound.b("Id")) { ++ uuid = nbttagcompound.a("Id"); ++ } ++ ++ try { ++ GameProfile gameprofile = new GameProfile(uuid, s); ++ ++ if (nbttagcompound.hasKeyOfType("Properties", 10)) { ++ NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Properties"); ++ Iterator iterator = nbttagcompound1.getKeys().iterator(); ++ ++ while (iterator.hasNext()) { ++ String s1 = (String) iterator.next(); ++ NBTTagList nbttaglist = nbttagcompound1.getList(s1, 10); ++ ++ for (int i = 0; i < nbttaglist.size(); ++i) { ++ NBTTagCompound nbttagcompound2 = nbttaglist.getCompound(i); ++ String s2 = nbttagcompound2.getString("Value"); ++ ++ if (nbttagcompound2.hasKeyOfType("Signature", 8)) { ++ gameprofile.getProperties().put(s1, new Property(s1, s2, nbttagcompound2.getString("Signature"))); ++ } else { ++ gameprofile.getProperties().put(s1, new Property(s1, s2)); ++ } ++ } ++ } ++ } ++ ++ return gameprofile; ++ } catch (Throwable throwable) { ++ return null; ++ } ++ } ++ ++ public static NBTTagCompound serialize(NBTTagCompound nbttagcompound, GameProfile gameprofile) { ++ if (!UtilColor.b(gameprofile.getName())) { ++ nbttagcompound.setString("Name", gameprofile.getName()); ++ } ++ ++ if (gameprofile.getId() != null) { ++ nbttagcompound.a("Id", gameprofile.getId()); ++ } ++ ++ if (!gameprofile.getProperties().isEmpty()) { ++ NBTTagCompound nbttagcompound1 = new NBTTagCompound(); ++ Iterator iterator = gameprofile.getProperties().keySet().iterator(); ++ ++ while (iterator.hasNext()) { ++ String s = (String) iterator.next(); ++ NBTTagList nbttaglist = new NBTTagList(); ++ ++ NBTTagCompound nbttagcompound2; ++ ++ for (Iterator iterator1 = gameprofile.getProperties().get(s).iterator(); iterator1.hasNext(); nbttaglist.add(nbttagcompound2)) { ++ Property property = (Property) iterator1.next(); ++ ++ nbttagcompound2 = new NBTTagCompound(); ++ nbttagcompound2.setString("Value", property.getValue()); ++ if (property.hasSignature()) { ++ nbttagcompound2.setString("Signature", property.getSignature()); ++ } ++ } ++ ++ nbttagcompound1.set(s, nbttaglist); ++ } ++ ++ nbttagcompound.set("Properties", nbttagcompound1); ++ } ++ ++ return nbttagcompound; ++ } ++ ++ @VisibleForTesting ++ public static boolean a(@Nullable NBTBase nbtbase, @Nullable NBTBase nbtbase1, boolean flag) { ++ if (nbtbase == nbtbase1) { ++ return true; ++ } else if (nbtbase == null) { ++ return true; ++ } else if (nbtbase1 == null) { ++ return false; ++ } else if (!nbtbase.getClass().equals(nbtbase1.getClass())) { ++ return false; ++ } else if (nbtbase instanceof NBTTagCompound) { ++ NBTTagCompound nbttagcompound = (NBTTagCompound) nbtbase; ++ NBTTagCompound nbttagcompound1 = (NBTTagCompound) nbtbase1; ++ Iterator iterator = nbttagcompound.getKeys().iterator(); ++ ++ String s; ++ NBTBase nbtbase2; ++ ++ do { ++ if (!iterator.hasNext()) { ++ return true; ++ } ++ ++ s = (String) iterator.next(); ++ nbtbase2 = nbttagcompound.get(s); ++ } while (a(nbtbase2, nbttagcompound1.get(s), flag)); ++ ++ return false; ++ } else if (nbtbase instanceof NBTTagList && flag) { ++ NBTTagList nbttaglist = (NBTTagList) nbtbase; ++ NBTTagList nbttaglist1 = (NBTTagList) nbtbase1; ++ ++ if (nbttaglist.isEmpty()) { ++ return nbttaglist1.isEmpty(); ++ } else { ++ int i = 0; ++ ++ while (i < nbttaglist.size()) { ++ NBTBase nbtbase3 = nbttaglist.get(i); ++ boolean flag1 = false; ++ int j = 0; ++ ++ while (true) { ++ if (j < nbttaglist1.size()) { ++ if (!a(nbtbase3, nbttaglist1.get(j), flag)) { ++ ++j; ++ continue; ++ } ++ ++ flag1 = true; ++ } ++ ++ if (!flag1) { ++ return false; ++ } ++ ++ ++i; ++ break; ++ } ++ } ++ ++ return true; ++ } ++ } else { ++ return nbtbase.equals(nbtbase1); ++ } ++ } ++ ++ public static NBTTagIntArray a(UUID uuid) { ++ return new NBTTagIntArray(MinecraftSerializableUUID.a(uuid)); ++ } ++ ++ public static UUID a(NBTBase nbtbase) { ++ if (nbtbase.b() != NBTTagIntArray.a) { ++ throw new IllegalArgumentException("Expected UUID-Tag to be of type " + NBTTagIntArray.a.a() + ", but found " + nbtbase.b().a() + "."); ++ } else { ++ int[] aint = ((NBTTagIntArray) nbtbase).getInts(); ++ ++ if (aint.length != 4) { ++ throw new IllegalArgumentException("Expected UUID-Array to be of length 4, but found " + aint.length + "."); ++ } else { ++ return MinecraftSerializableUUID.a(aint); ++ } ++ } ++ } ++ ++ public static BlockPosition b(NBTTagCompound nbttagcompound) { ++ return new BlockPosition(nbttagcompound.getInt("X"), nbttagcompound.getInt("Y"), nbttagcompound.getInt("Z")); ++ } ++ ++ public static NBTTagCompound a(BlockPosition blockposition) { ++ NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ ++ nbttagcompound.setInt("X", blockposition.getX()); ++ nbttagcompound.setInt("Y", blockposition.getY()); ++ nbttagcompound.setInt("Z", blockposition.getZ()); ++ return nbttagcompound; ++ } ++ ++ public static IBlockData c(NBTTagCompound nbttagcompound) { ++ if (!nbttagcompound.hasKeyOfType("Name", 8)) { ++ return Blocks.AIR.getBlockData(); ++ } else { ++ Block block = (Block) IRegistry.BLOCK.get(new MinecraftKey(nbttagcompound.getString("Name"))); ++ IBlockData iblockdata = block.getBlockData(); ++ ++ if (nbttagcompound.hasKeyOfType("Properties", 10)) { ++ NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Properties"); ++ BlockStateList blockstatelist = block.getStates(); ++ Iterator iterator = nbttagcompound1.getKeys().iterator(); ++ ++ while (iterator.hasNext()) { ++ String s = (String) iterator.next(); ++ IBlockState iblockstate = blockstatelist.a(s); ++ ++ if (iblockstate != null) { ++ iblockdata = (IBlockData) a(iblockdata, iblockstate, s, nbttagcompound1, nbttagcompound); ++ } ++ } ++ } ++ ++ return iblockdata; ++ } ++ } ++ ++ private static , T extends Comparable> S a(S s0, IBlockState iblockstate, String s, NBTTagCompound nbttagcompound, NBTTagCompound nbttagcompound1) { ++ Optional optional = iblockstate.b(nbttagcompound.getString(s)); ++ ++ if (optional.isPresent()) { ++ return (IBlockDataHolder) s0.set(iblockstate, (Comparable) optional.get()); ++ } else { ++ GameProfileSerializer.LOGGER.warn("Unable to read property: {} with value: {} for blockstate: {}", s, nbttagcompound.getString(s), nbttagcompound1.toString()); ++ return s0; ++ } ++ } ++ ++ public static NBTTagCompound a(IBlockData iblockdata) { ++ NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ ++ nbttagcompound.setString("Name", IRegistry.BLOCK.getKey(iblockdata.getBlock()).toString()); ++ ImmutableMap, Comparable> immutablemap = iblockdata.getStateMap(); ++ ++ if (!immutablemap.isEmpty()) { ++ NBTTagCompound nbttagcompound1 = new NBTTagCompound(); ++ UnmodifiableIterator unmodifiableiterator = immutablemap.entrySet().iterator(); ++ ++ while (unmodifiableiterator.hasNext()) { ++ Entry, Comparable> entry = (Entry) unmodifiableiterator.next(); ++ IBlockState iblockstate = (IBlockState) entry.getKey(); ++ ++ nbttagcompound1.setString(iblockstate.getName(), a(iblockstate, (Comparable) entry.getValue())); ++ } ++ ++ nbttagcompound.set("Properties", nbttagcompound1); ++ } ++ ++ return nbttagcompound; ++ } ++ ++ private static > String a(IBlockState iblockstate, Comparable comparable) { ++ return iblockstate.a(comparable); ++ } ++ ++ public static NBTTagCompound a(DataFixer datafixer, DataFixTypes datafixtypes, NBTTagCompound nbttagcompound, int i) { ++ return a(datafixer, datafixtypes, nbttagcompound, i, SharedConstants.getGameVersion().getWorldVersion()); ++ } ++ ++ public static NBTTagCompound a(DataFixer datafixer, DataFixTypes datafixtypes, NBTTagCompound nbttagcompound, int i, int j) { ++ return (NBTTagCompound) datafixer.update(datafixtypes.a(), new Dynamic(DynamicOpsNBT.a, nbttagcompound), i, j).getValue(); ++ } ++} +diff --git a/src/main/java/net/minecraft/nbt/NBTBase.java b/src/main/java/net/minecraft/nbt/NBTBase.java +new file mode 100644 +index 0000000000000000000000000000000000000000..170a65cb13e7b87f64cd28331431ba55d53702cd +--- /dev/null ++++ b/src/main/java/net/minecraft/nbt/NBTBase.java +@@ -0,0 +1,34 @@ ++package net.minecraft.nbt; ++ ++import java.io.DataOutput; ++import java.io.IOException; ++import net.minecraft.EnumChatFormat; ++import net.minecraft.network.chat.IChatBaseComponent; ++ ++public interface NBTBase { ++ ++ EnumChatFormat d = EnumChatFormat.AQUA; ++ EnumChatFormat e = EnumChatFormat.GREEN; ++ EnumChatFormat f = EnumChatFormat.GOLD; ++ EnumChatFormat g = EnumChatFormat.RED; ++ ++ void write(DataOutput dataoutput) throws IOException; ++ ++ String toString(); ++ ++ byte getTypeId(); ++ ++ NBTTagType b(); ++ ++ NBTBase clone(); ++ ++ default String asString() { ++ return this.toString(); ++ } ++ ++ default IChatBaseComponent l() { ++ return this.a("", 0); ++ } ++ ++ IChatBaseComponent a(String s, int i); ++} +diff --git a/src/main/java/net/minecraft/nbt/NBTTagCompound.java b/src/main/java/net/minecraft/nbt/NBTTagCompound.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b2fb24e9ae19ab6e7039a98fc0c265f801be8a99 +--- /dev/null ++++ b/src/main/java/net/minecraft/nbt/NBTTagCompound.java +@@ -0,0 +1,536 @@ ++package net.minecraft.nbt; ++ ++import com.google.common.base.Strings; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.DataResult; ++import com.mojang.serialization.Dynamic; ++import java.io.DataInput; ++import java.io.DataOutput; ++import java.io.IOException; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Objects; ++import java.util.Set; ++import java.util.UUID; ++import java.util.regex.Pattern; ++import javax.annotation.Nullable; ++import net.minecraft.CrashReport; ++import net.minecraft.CrashReportSystemDetails; ++import net.minecraft.ReportedException; ++import net.minecraft.network.chat.ChatComponentText; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.network.chat.IChatMutableComponent; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class NBTTagCompound implements NBTBase { ++ ++ public static final Codec a = Codec.PASSTHROUGH.comapFlatMap((dynamic) -> { ++ NBTBase nbtbase = (NBTBase) dynamic.convert(DynamicOpsNBT.a).getValue(); ++ ++ return nbtbase instanceof NBTTagCompound ? DataResult.success((NBTTagCompound) nbtbase) : DataResult.error("Not a compound tag: " + nbtbase); ++ }, (nbttagcompound) -> { ++ return new Dynamic(DynamicOpsNBT.a, nbttagcompound); ++ }); ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private static final Pattern h = Pattern.compile("[A-Za-z0-9._+-]+"); ++ public static final NBTTagType b = new NBTTagType() { ++ @Override ++ public NBTTagCompound b(DataInput datainput, int i, NBTReadLimiter nbtreadlimiter) throws IOException { ++ nbtreadlimiter.a(384L); ++ if (i > 512) { ++ throw new RuntimeException("Tried to read NBT tag with too high complexity, depth > 512"); ++ } else { ++ HashMap hashmap = Maps.newHashMap(); ++ ++ byte b0; ++ ++ while ((b0 = NBTTagCompound.c(datainput, nbtreadlimiter)) != 0) { ++ String s = NBTTagCompound.d(datainput, nbtreadlimiter); ++ ++ nbtreadlimiter.a((long) (224 + 16 * s.length())); ++ NBTBase nbtbase = NBTTagCompound.b(NBTTagTypes.a(b0), s, datainput, i + 1, nbtreadlimiter); ++ ++ if (hashmap.put(s, nbtbase) != null) { ++ nbtreadlimiter.a(288L); ++ } ++ } ++ ++ return new NBTTagCompound(hashmap); ++ } ++ } ++ ++ @Override ++ public String a() { ++ return "COMPOUND"; ++ } ++ ++ @Override ++ public String b() { ++ return "TAG_Compound"; ++ } ++ }; ++ private final Map map; ++ ++ protected NBTTagCompound(Map map) { ++ this.map = map; ++ } ++ ++ public NBTTagCompound() { ++ this(Maps.newHashMap()); ++ } ++ ++ @Override ++ public void write(DataOutput dataoutput) throws IOException { ++ Iterator iterator = this.map.keySet().iterator(); ++ ++ while (iterator.hasNext()) { ++ String s = (String) iterator.next(); ++ NBTBase nbtbase = (NBTBase) this.map.get(s); ++ ++ a(s, nbtbase, dataoutput); ++ } ++ ++ dataoutput.writeByte(0); ++ } ++ ++ public Set getKeys() { ++ return this.map.keySet(); ++ } ++ ++ @Override ++ public byte getTypeId() { ++ return 10; ++ } ++ ++ @Override ++ public NBTTagType b() { ++ return NBTTagCompound.b; ++ } ++ ++ public int e() { ++ return this.map.size(); ++ } ++ ++ @Nullable ++ public NBTBase set(String s, NBTBase nbtbase) { ++ return (NBTBase) this.map.put(s, nbtbase); ++ } ++ ++ public void setByte(String s, byte b0) { ++ this.map.put(s, NBTTagByte.a(b0)); ++ } ++ ++ public void setShort(String s, short short0) { ++ this.map.put(s, NBTTagShort.a(short0)); ++ } ++ ++ public void setInt(String s, int i) { ++ this.map.put(s, NBTTagInt.a(i)); ++ } ++ ++ public void setLong(String s, long i) { ++ this.map.put(s, NBTTagLong.a(i)); ++ } ++ ++ public void a(String s, UUID uuid) { ++ this.map.put(s, GameProfileSerializer.a(uuid)); ++ } ++ ++ public UUID a(String s) { ++ return GameProfileSerializer.a(this.get(s)); ++ } ++ ++ public boolean b(String s) { ++ NBTBase nbtbase = this.get(s); ++ ++ return nbtbase != null && nbtbase.b() == NBTTagIntArray.a && ((NBTTagIntArray) nbtbase).getInts().length == 4; ++ } ++ ++ public void setFloat(String s, float f) { ++ this.map.put(s, NBTTagFloat.a(f)); ++ } ++ ++ public void setDouble(String s, double d0) { ++ this.map.put(s, NBTTagDouble.a(d0)); ++ } ++ ++ public void setString(String s, String s1) { ++ this.map.put(s, NBTTagString.a(s1)); ++ } ++ ++ public void setByteArray(String s, byte[] abyte) { ++ this.map.put(s, new NBTTagByteArray(abyte)); ++ } ++ ++ public void setIntArray(String s, int[] aint) { ++ this.map.put(s, new NBTTagIntArray(aint)); ++ } ++ ++ public void b(String s, List list) { ++ this.map.put(s, new NBTTagIntArray(list)); ++ } ++ ++ public void a(String s, long[] along) { ++ this.map.put(s, new NBTTagLongArray(along)); ++ } ++ ++ public void c(String s, List list) { ++ this.map.put(s, new NBTTagLongArray(list)); ++ } ++ ++ public void setBoolean(String s, boolean flag) { ++ this.map.put(s, NBTTagByte.a(flag)); ++ } ++ ++ @Nullable ++ public NBTBase get(String s) { ++ return (NBTBase) this.map.get(s); ++ } ++ ++ public byte d(String s) { ++ NBTBase nbtbase = (NBTBase) this.map.get(s); ++ ++ return nbtbase == null ? 0 : nbtbase.getTypeId(); ++ } ++ ++ public boolean hasKey(String s) { ++ return this.map.containsKey(s); ++ } ++ ++ public boolean hasKeyOfType(String s, int i) { ++ byte b0 = this.d(s); ++ ++ return b0 == i ? true : (i != 99 ? false : b0 == 1 || b0 == 2 || b0 == 3 || b0 == 4 || b0 == 5 || b0 == 6); ++ } ++ ++ public byte getByte(String s) { ++ try { ++ if (this.hasKeyOfType(s, 99)) { ++ return ((NBTNumber) this.map.get(s)).asByte(); ++ } ++ } catch (ClassCastException classcastexception) { ++ ; ++ } ++ ++ return 0; ++ } ++ ++ public short getShort(String s) { ++ try { ++ if (this.hasKeyOfType(s, 99)) { ++ return ((NBTNumber) this.map.get(s)).asShort(); ++ } ++ } catch (ClassCastException classcastexception) { ++ ; ++ } ++ ++ return 0; ++ } ++ ++ public int getInt(String s) { ++ try { ++ if (this.hasKeyOfType(s, 99)) { ++ return ((NBTNumber) this.map.get(s)).asInt(); ++ } ++ } catch (ClassCastException classcastexception) { ++ ; ++ } ++ ++ return 0; ++ } ++ ++ public long getLong(String s) { ++ try { ++ if (this.hasKeyOfType(s, 99)) { ++ return ((NBTNumber) this.map.get(s)).asLong(); ++ } ++ } catch (ClassCastException classcastexception) { ++ ; ++ } ++ ++ return 0L; ++ } ++ ++ public float getFloat(String s) { ++ try { ++ if (this.hasKeyOfType(s, 99)) { ++ return ((NBTNumber) this.map.get(s)).asFloat(); ++ } ++ } catch (ClassCastException classcastexception) { ++ ; ++ } ++ ++ return 0.0F; ++ } ++ ++ public double getDouble(String s) { ++ try { ++ if (this.hasKeyOfType(s, 99)) { ++ return ((NBTNumber) this.map.get(s)).asDouble(); ++ } ++ } catch (ClassCastException classcastexception) { ++ ; ++ } ++ ++ return 0.0D; ++ } ++ ++ public String getString(String s) { ++ try { ++ if (this.hasKeyOfType(s, 8)) { ++ return ((NBTBase) this.map.get(s)).asString(); ++ } ++ } catch (ClassCastException classcastexception) { ++ ; ++ } ++ ++ return ""; ++ } ++ ++ public byte[] getByteArray(String s) { ++ try { ++ if (this.hasKeyOfType(s, 7)) { ++ return ((NBTTagByteArray) this.map.get(s)).getBytes(); ++ } ++ } catch (ClassCastException classcastexception) { ++ throw new ReportedException(this.a(s, NBTTagByteArray.a, classcastexception)); ++ } ++ ++ return new byte[0]; ++ } ++ ++ public int[] getIntArray(String s) { ++ try { ++ if (this.hasKeyOfType(s, 11)) { ++ return ((NBTTagIntArray) this.map.get(s)).getInts(); ++ } ++ } catch (ClassCastException classcastexception) { ++ throw new ReportedException(this.a(s, NBTTagIntArray.a, classcastexception)); ++ } ++ ++ return new int[0]; ++ } ++ ++ public long[] getLongArray(String s) { ++ try { ++ if (this.hasKeyOfType(s, 12)) { ++ return ((NBTTagLongArray) this.map.get(s)).getLongs(); ++ } ++ } catch (ClassCastException classcastexception) { ++ throw new ReportedException(this.a(s, NBTTagLongArray.a, classcastexception)); ++ } ++ ++ return new long[0]; ++ } ++ ++ public NBTTagCompound getCompound(String s) { ++ try { ++ if (this.hasKeyOfType(s, 10)) { ++ return (NBTTagCompound) this.map.get(s); ++ } ++ } catch (ClassCastException classcastexception) { ++ throw new ReportedException(this.a(s, NBTTagCompound.b, classcastexception)); ++ } ++ ++ return new NBTTagCompound(); ++ } ++ ++ public NBTTagList getList(String s, int i) { ++ try { ++ if (this.d(s) == 9) { ++ NBTTagList nbttaglist = (NBTTagList) this.map.get(s); ++ ++ if (!nbttaglist.isEmpty() && nbttaglist.d_() != i) { ++ return new NBTTagList(); ++ } ++ ++ return nbttaglist; ++ } ++ } catch (ClassCastException classcastexception) { ++ throw new ReportedException(this.a(s, NBTTagList.a, classcastexception)); ++ } ++ ++ return new NBTTagList(); ++ } ++ ++ public boolean getBoolean(String s) { ++ return this.getByte(s) != 0; ++ } ++ ++ public void remove(String s) { ++ this.map.remove(s); ++ } ++ ++ @Override ++ public String toString() { ++ StringBuilder stringbuilder = new StringBuilder("{"); ++ Collection collection = this.map.keySet(); ++ ++ if (NBTTagCompound.LOGGER.isDebugEnabled()) { ++ List list = Lists.newArrayList(this.map.keySet()); ++ ++ Collections.sort(list); ++ collection = list; ++ } ++ ++ String s; ++ ++ for (Iterator iterator = ((Collection) collection).iterator(); iterator.hasNext(); stringbuilder.append(s(s)).append(':').append(this.map.get(s))) { ++ s = (String) iterator.next(); ++ if (stringbuilder.length() != 1) { ++ stringbuilder.append(','); ++ } ++ } ++ ++ return stringbuilder.append('}').toString(); ++ } ++ ++ public boolean isEmpty() { ++ return this.map.isEmpty(); ++ } ++ ++ private CrashReport a(String s, NBTTagType nbttagtype, ClassCastException classcastexception) { ++ CrashReport crashreport = CrashReport.a(classcastexception, "Reading NBT data"); ++ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Corrupt NBT tag", 1); ++ ++ crashreportsystemdetails.a("Tag type found", () -> { ++ return ((NBTBase) this.map.get(s)).b().a(); ++ }); ++ crashreportsystemdetails.a("Tag type expected", nbttagtype::a); ++ crashreportsystemdetails.a("Tag name", (Object) s); ++ return crashreport; ++ } ++ ++ @Override ++ public NBTTagCompound clone() { ++ Map map = Maps.newHashMap(Maps.transformValues(this.map, NBTBase::clone)); ++ ++ return new NBTTagCompound(map); ++ } ++ ++ public boolean equals(Object object) { ++ return this == object ? true : object instanceof NBTTagCompound && Objects.equals(this.map, ((NBTTagCompound) object).map); ++ } ++ ++ public int hashCode() { ++ return this.map.hashCode(); ++ } ++ ++ private static void a(String s, NBTBase nbtbase, DataOutput dataoutput) throws IOException { ++ dataoutput.writeByte(nbtbase.getTypeId()); ++ if (nbtbase.getTypeId() != 0) { ++ dataoutput.writeUTF(s); ++ nbtbase.write(dataoutput); ++ } ++ } ++ ++ private static byte c(DataInput datainput, NBTReadLimiter nbtreadlimiter) throws IOException { ++ return datainput.readByte(); ++ } ++ ++ private static String d(DataInput datainput, NBTReadLimiter nbtreadlimiter) throws IOException { ++ return datainput.readUTF(); ++ } ++ ++ private static NBTBase b(NBTTagType nbttagtype, String s, DataInput datainput, int i, NBTReadLimiter nbtreadlimiter) { ++ try { ++ return nbttagtype.b(datainput, i, nbtreadlimiter); ++ } catch (IOException ioexception) { ++ CrashReport crashreport = CrashReport.a(ioexception, "Loading NBT data"); ++ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("NBT Tag"); ++ ++ crashreportsystemdetails.a("Tag name", (Object) s); ++ crashreportsystemdetails.a("Tag type", (Object) nbttagtype.a()); ++ throw new ReportedException(crashreport); ++ } ++ } ++ ++ public NBTTagCompound a(NBTTagCompound nbttagcompound) { ++ Iterator iterator = nbttagcompound.map.keySet().iterator(); ++ ++ while (iterator.hasNext()) { ++ String s = (String) iterator.next(); ++ NBTBase nbtbase = (NBTBase) nbttagcompound.map.get(s); ++ ++ if (nbtbase.getTypeId() == 10) { ++ if (this.hasKeyOfType(s, 10)) { ++ NBTTagCompound nbttagcompound1 = this.getCompound(s); ++ ++ nbttagcompound1.a((NBTTagCompound) nbtbase); ++ } else { ++ this.set(s, nbtbase.clone()); ++ } ++ } else { ++ this.set(s, nbtbase.clone()); ++ } ++ } ++ ++ return this; ++ } ++ ++ protected static String s(String s) { ++ return NBTTagCompound.h.matcher(s).matches() ? s : NBTTagString.b(s); ++ } ++ ++ protected static IChatBaseComponent t(String s) { ++ if (NBTTagCompound.h.matcher(s).matches()) { ++ return (new ChatComponentText(s)).a(NBTTagCompound.d); ++ } else { ++ String s1 = NBTTagString.b(s); ++ String s2 = s1.substring(0, 1); ++ IChatMutableComponent ichatmutablecomponent = (new ChatComponentText(s1.substring(1, s1.length() - 1))).a(NBTTagCompound.d); ++ ++ return (new ChatComponentText(s2)).addSibling(ichatmutablecomponent).c(s2); ++ } ++ } ++ ++ @Override ++ public IChatBaseComponent a(String s, int i) { ++ if (this.map.isEmpty()) { ++ return new ChatComponentText("{}"); ++ } else { ++ ChatComponentText chatcomponenttext = new ChatComponentText("{"); ++ Collection collection = this.map.keySet(); ++ ++ if (NBTTagCompound.LOGGER.isDebugEnabled()) { ++ List list = Lists.newArrayList(this.map.keySet()); ++ ++ Collections.sort(list); ++ collection = list; ++ } ++ ++ if (!s.isEmpty()) { ++ chatcomponenttext.c("\n"); ++ } ++ ++ IChatMutableComponent ichatmutablecomponent; ++ ++ for (Iterator iterator = ((Collection) collection).iterator(); iterator.hasNext(); chatcomponenttext.addSibling(ichatmutablecomponent)) { ++ String s1 = (String) iterator.next(); ++ ++ ichatmutablecomponent = (new ChatComponentText(Strings.repeat(s, i + 1))).addSibling(t(s1)).c(String.valueOf(':')).c(" ").addSibling(((NBTBase) this.map.get(s1)).a(s, i + 1)); ++ if (iterator.hasNext()) { ++ ichatmutablecomponent.c(String.valueOf(',')).c(s.isEmpty() ? " " : "\n"); ++ } ++ } ++ ++ if (!s.isEmpty()) { ++ chatcomponenttext.c("\n").c(Strings.repeat(s, i)); ++ } ++ ++ chatcomponenttext.c("}"); ++ return chatcomponenttext; ++ } ++ } ++ ++ protected Map h() { ++ return Collections.unmodifiableMap(this.map); ++ } ++} +diff --git a/src/main/java/net/minecraft/nbt/NBTTagList.java b/src/main/java/net/minecraft/nbt/NBTTagList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..35cca76fb7c7aa736e64185b44016e65cfaef6cd +--- /dev/null ++++ b/src/main/java/net/minecraft/nbt/NBTTagList.java +@@ -0,0 +1,360 @@ ++package net.minecraft.nbt; ++ ++import com.google.common.base.Strings; ++import com.google.common.collect.Iterables; ++import com.google.common.collect.Lists; ++import it.unimi.dsi.fastutil.bytes.ByteOpenHashSet; ++import it.unimi.dsi.fastutil.bytes.ByteSet; ++import java.io.DataInput; ++import java.io.DataOutput; ++import java.io.IOException; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Objects; ++import net.minecraft.network.chat.ChatComponentText; ++import net.minecraft.network.chat.IChatBaseComponent; ++ ++public class NBTTagList extends NBTList { ++ ++ public static final NBTTagType a = new NBTTagType() { ++ @Override ++ public NBTTagList b(DataInput datainput, int i, NBTReadLimiter nbtreadlimiter) throws IOException { ++ nbtreadlimiter.a(296L); ++ if (i > 512) { ++ throw new RuntimeException("Tried to read NBT tag with too high complexity, depth > 512"); ++ } else { ++ byte b0 = datainput.readByte(); ++ int j = datainput.readInt(); ++ ++ if (b0 == 0 && j > 0) { ++ throw new RuntimeException("Missing type on ListTag"); ++ } else { ++ nbtreadlimiter.a(32L * (long) j); ++ NBTTagType nbttagtype = NBTTagTypes.a(b0); ++ List list = Lists.newArrayListWithCapacity(j); ++ ++ for (int k = 0; k < j; ++k) { ++ list.add(nbttagtype.b(datainput, i + 1, nbtreadlimiter)); ++ } ++ ++ return new NBTTagList(list, b0); ++ } ++ } ++ } ++ ++ @Override ++ public String a() { ++ return "LIST"; ++ } ++ ++ @Override ++ public String b() { ++ return "TAG_List"; ++ } ++ }; ++ private static final ByteSet b = new ByteOpenHashSet(Arrays.asList(1, 2, 3, 4, 5, 6)); ++ private final List list; ++ private byte type; ++ ++ private NBTTagList(List list, byte b0) { ++ this.list = list; ++ this.type = b0; ++ } ++ ++ public NBTTagList() { ++ this(Lists.newArrayList(), (byte) 0); ++ } ++ ++ @Override ++ public void write(DataOutput dataoutput) throws IOException { ++ if (this.list.isEmpty()) { ++ this.type = 0; ++ } else { ++ this.type = ((NBTBase) this.list.get(0)).getTypeId(); ++ } ++ ++ dataoutput.writeByte(this.type); ++ dataoutput.writeInt(this.list.size()); ++ Iterator iterator = this.list.iterator(); ++ ++ while (iterator.hasNext()) { ++ NBTBase nbtbase = (NBTBase) iterator.next(); ++ ++ nbtbase.write(dataoutput); ++ } ++ ++ } ++ ++ @Override ++ public byte getTypeId() { ++ return 9; ++ } ++ ++ @Override ++ public NBTTagType b() { ++ return NBTTagList.a; ++ } ++ ++ @Override ++ public String toString() { ++ StringBuilder stringbuilder = new StringBuilder("["); ++ ++ for (int i = 0; i < this.list.size(); ++i) { ++ if (i != 0) { ++ stringbuilder.append(','); ++ } ++ ++ stringbuilder.append(this.list.get(i)); ++ } ++ ++ return stringbuilder.append(']').toString(); ++ } ++ ++ private void g() { ++ if (this.list.isEmpty()) { ++ this.type = 0; ++ } ++ ++ } ++ ++ @Override ++ public NBTBase remove(int i) { ++ NBTBase nbtbase = (NBTBase) this.list.remove(i); ++ ++ this.g(); ++ return nbtbase; ++ } ++ ++ public boolean isEmpty() { ++ return this.list.isEmpty(); ++ } ++ ++ public NBTTagCompound getCompound(int i) { ++ if (i >= 0 && i < this.list.size()) { ++ NBTBase nbtbase = (NBTBase) this.list.get(i); ++ ++ if (nbtbase.getTypeId() == 10) { ++ return (NBTTagCompound) nbtbase; ++ } ++ } ++ ++ return new NBTTagCompound(); ++ } ++ ++ public NBTTagList b(int i) { ++ if (i >= 0 && i < this.list.size()) { ++ NBTBase nbtbase = (NBTBase) this.list.get(i); ++ ++ if (nbtbase.getTypeId() == 9) { ++ return (NBTTagList) nbtbase; ++ } ++ } ++ ++ return new NBTTagList(); ++ } ++ ++ public short d(int i) { ++ if (i >= 0 && i < this.list.size()) { ++ NBTBase nbtbase = (NBTBase) this.list.get(i); ++ ++ if (nbtbase.getTypeId() == 2) { ++ return ((NBTTagShort) nbtbase).asShort(); ++ } ++ } ++ ++ return 0; ++ } ++ ++ public int e(int i) { ++ if (i >= 0 && i < this.list.size()) { ++ NBTBase nbtbase = (NBTBase) this.list.get(i); ++ ++ if (nbtbase.getTypeId() == 3) { ++ return ((NBTTagInt) nbtbase).asInt(); ++ } ++ } ++ ++ return 0; ++ } ++ ++ public int[] f(int i) { ++ if (i >= 0 && i < this.list.size()) { ++ NBTBase nbtbase = (NBTBase) this.list.get(i); ++ ++ if (nbtbase.getTypeId() == 11) { ++ return ((NBTTagIntArray) nbtbase).getInts(); ++ } ++ } ++ ++ return new int[0]; ++ } ++ ++ public double h(int i) { ++ if (i >= 0 && i < this.list.size()) { ++ NBTBase nbtbase = (NBTBase) this.list.get(i); ++ ++ if (nbtbase.getTypeId() == 6) { ++ return ((NBTTagDouble) nbtbase).asDouble(); ++ } ++ } ++ ++ return 0.0D; ++ } ++ ++ public float i(int i) { ++ if (i >= 0 && i < this.list.size()) { ++ NBTBase nbtbase = (NBTBase) this.list.get(i); ++ ++ if (nbtbase.getTypeId() == 5) { ++ return ((NBTTagFloat) nbtbase).asFloat(); ++ } ++ } ++ ++ return 0.0F; ++ } ++ ++ public String getString(int i) { ++ if (i >= 0 && i < this.list.size()) { ++ NBTBase nbtbase = (NBTBase) this.list.get(i); ++ ++ return nbtbase.getTypeId() == 8 ? nbtbase.asString() : nbtbase.toString(); ++ } else { ++ return ""; ++ } ++ } ++ ++ public int size() { ++ return this.list.size(); ++ } ++ ++ public NBTBase get(int i) { ++ return (NBTBase) this.list.get(i); ++ } ++ ++ @Override ++ public NBTBase set(int i, NBTBase nbtbase) { ++ NBTBase nbtbase1 = this.get(i); ++ ++ if (!this.a(i, nbtbase)) { ++ throw new UnsupportedOperationException(String.format("Trying to add tag of type %d to list of %d", nbtbase.getTypeId(), this.type)); ++ } else { ++ return nbtbase1; ++ } ++ } ++ ++ @Override ++ public void add(int i, NBTBase nbtbase) { ++ if (!this.b(i, nbtbase)) { ++ throw new UnsupportedOperationException(String.format("Trying to add tag of type %d to list of %d", nbtbase.getTypeId(), this.type)); ++ } ++ } ++ ++ @Override ++ public boolean a(int i, NBTBase nbtbase) { ++ if (this.a(nbtbase)) { ++ this.list.set(i, nbtbase); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ @Override ++ public boolean b(int i, NBTBase nbtbase) { ++ if (this.a(nbtbase)) { ++ this.list.add(i, nbtbase); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ private boolean a(NBTBase nbtbase) { ++ if (nbtbase.getTypeId() == 0) { ++ return false; ++ } else if (this.type == 0) { ++ this.type = nbtbase.getTypeId(); ++ return true; ++ } else { ++ return this.type == nbtbase.getTypeId(); ++ } ++ } ++ ++ @Override ++ public NBTTagList clone() { ++ Iterable iterable = NBTTagTypes.a(this.type).c() ? this.list : Iterables.transform(this.list, NBTBase::clone); ++ List list = Lists.newArrayList((Iterable) iterable); ++ ++ return new NBTTagList(list, this.type); ++ } ++ ++ public boolean equals(Object object) { ++ return this == object ? true : object instanceof NBTTagList && Objects.equals(this.list, ((NBTTagList) object).list); ++ } ++ ++ public int hashCode() { ++ return this.list.hashCode(); ++ } ++ ++ @Override ++ public IChatBaseComponent a(String s, int i) { ++ if (this.isEmpty()) { ++ return new ChatComponentText("[]"); ++ } else { ++ int j; ++ ++ if (NBTTagList.b.contains(this.type) && this.size() <= 8) { ++ String s1 = ", "; ++ ChatComponentText chatcomponenttext = new ChatComponentText("["); ++ ++ for (j = 0; j < this.list.size(); ++j) { ++ if (j != 0) { ++ chatcomponenttext.c(", "); ++ } ++ ++ chatcomponenttext.addSibling(((NBTBase) this.list.get(j)).l()); ++ } ++ ++ chatcomponenttext.c("]"); ++ return chatcomponenttext; ++ } else { ++ ChatComponentText chatcomponenttext1 = new ChatComponentText("["); ++ ++ if (!s.isEmpty()) { ++ chatcomponenttext1.c("\n"); ++ } ++ ++ String s2 = String.valueOf(','); ++ ++ for (j = 0; j < this.list.size(); ++j) { ++ ChatComponentText chatcomponenttext2 = new ChatComponentText(Strings.repeat(s, i + 1)); ++ ++ chatcomponenttext2.addSibling(((NBTBase) this.list.get(j)).a(s, i + 1)); ++ if (j != this.list.size() - 1) { ++ chatcomponenttext2.c(s2).c(s.isEmpty() ? " " : "\n"); ++ } ++ ++ chatcomponenttext1.addSibling(chatcomponenttext2); ++ } ++ ++ if (!s.isEmpty()) { ++ chatcomponenttext1.c("\n").c(Strings.repeat(s, i)); ++ } ++ ++ chatcomponenttext1.c("]"); ++ return chatcomponenttext1; ++ } ++ } ++ } ++ ++ @Override ++ public byte d_() { ++ return this.type; ++ } ++ ++ public void clear() { ++ this.list.clear(); ++ this.type = 0; ++ } ++} +diff --git a/src/main/java/net/minecraft/nbt/NBTTagString.java b/src/main/java/net/minecraft/nbt/NBTTagString.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e26ef49d9dde8ed0fb4267b48cb597563967f313 +--- /dev/null ++++ b/src/main/java/net/minecraft/nbt/NBTTagString.java +@@ -0,0 +1,127 @@ ++package net.minecraft.nbt; ++ ++import java.io.DataInput; ++import java.io.DataOutput; ++import java.io.IOException; ++import java.util.Objects; ++import net.minecraft.network.chat.ChatComponentText; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.network.chat.IChatMutableComponent; ++ ++public class NBTTagString implements NBTBase { ++ ++ public static final NBTTagType a = new NBTTagType() { ++ @Override ++ public NBTTagString b(DataInput datainput, int i, NBTReadLimiter nbtreadlimiter) throws IOException { ++ nbtreadlimiter.a(288L); ++ String s = datainput.readUTF(); ++ ++ nbtreadlimiter.a((long) (16 * s.length())); ++ return NBTTagString.a(s); ++ } ++ ++ @Override ++ public String a() { ++ return "STRING"; ++ } ++ ++ @Override ++ public String b() { ++ return "TAG_String"; ++ } ++ ++ @Override ++ public boolean c() { ++ return true; ++ } ++ }; ++ private static final NBTTagString b = new NBTTagString(""); ++ private final String data; ++ ++ private NBTTagString(String s) { ++ Objects.requireNonNull(s, "Null string not allowed"); ++ this.data = s; ++ } ++ ++ public static NBTTagString a(String s) { ++ return s.isEmpty() ? NBTTagString.b : new NBTTagString(s); ++ } ++ ++ @Override ++ public void write(DataOutput dataoutput) throws IOException { ++ dataoutput.writeUTF(this.data); ++ } ++ ++ @Override ++ public byte getTypeId() { ++ return 8; ++ } ++ ++ @Override ++ public NBTTagType b() { ++ return NBTTagString.a; ++ } ++ ++ @Override ++ public String toString() { ++ return b(this.data); ++ } ++ ++ @Override ++ public NBTTagString clone() { ++ return this; ++ } ++ ++ public boolean equals(Object object) { ++ return this == object ? true : object instanceof NBTTagString && Objects.equals(this.data, ((NBTTagString) object).data); ++ } ++ ++ public int hashCode() { ++ return this.data.hashCode(); ++ } ++ ++ @Override ++ public String asString() { ++ return this.data; ++ } ++ ++ @Override ++ public IChatBaseComponent a(String s, int i) { ++ String s1 = b(this.data); ++ String s2 = s1.substring(0, 1); ++ IChatMutableComponent ichatmutablecomponent = (new ChatComponentText(s1.substring(1, s1.length() - 1))).a(NBTTagString.e); ++ ++ return (new ChatComponentText(s2)).addSibling(ichatmutablecomponent).c(s2); ++ } ++ ++ public static String b(String s) { ++ StringBuilder stringbuilder = new StringBuilder(" "); ++ int i = 0; ++ ++ for (int j = 0; j < s.length(); ++j) { ++ char c0 = s.charAt(j); ++ ++ if (c0 == '\\') { ++ stringbuilder.append('\\'); ++ } else if (c0 == '"' || c0 == '\'') { ++ if (i == 0) { ++ i = c0 == '"' ? 39 : 34; ++ } ++ ++ if (i == c0) { ++ stringbuilder.append('\\'); ++ } ++ } ++ ++ stringbuilder.append(c0); ++ } ++ ++ if (i == 0) { ++ i = 34; ++ } ++ ++ stringbuilder.setCharAt(0, (char) i); ++ stringbuilder.append((char) i); ++ return stringbuilder.toString(); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/EnumProtocol.java b/src/main/java/net/minecraft/network/EnumProtocol.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ab08336043d4f558434ed1f38d25cc555ace1ac0 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/EnumProtocol.java +@@ -0,0 +1,290 @@ ++package net.minecraft.network; ++ ++import com.google.common.collect.Iterables; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import it.unimi.dsi.fastutil.objects.Object2IntMap; ++import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.function.Supplier; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.network.protocol.EnumProtocolDirection; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.game.PacketPlayInAbilities; ++import net.minecraft.network.protocol.game.PacketPlayInAdvancements; ++import net.minecraft.network.protocol.game.PacketPlayInArmAnimation; ++import net.minecraft.network.protocol.game.PacketPlayInAutoRecipe; ++import net.minecraft.network.protocol.game.PacketPlayInBEdit; ++import net.minecraft.network.protocol.game.PacketPlayInBeacon; ++import net.minecraft.network.protocol.game.PacketPlayInBlockDig; ++import net.minecraft.network.protocol.game.PacketPlayInBlockPlace; ++import net.minecraft.network.protocol.game.PacketPlayInBoatMove; ++import net.minecraft.network.protocol.game.PacketPlayInChat; ++import net.minecraft.network.protocol.game.PacketPlayInClientCommand; ++import net.minecraft.network.protocol.game.PacketPlayInCloseWindow; ++import net.minecraft.network.protocol.game.PacketPlayInCustomPayload; ++import net.minecraft.network.protocol.game.PacketPlayInDifficultyChange; ++import net.minecraft.network.protocol.game.PacketPlayInDifficultyLock; ++import net.minecraft.network.protocol.game.PacketPlayInEnchantItem; ++import net.minecraft.network.protocol.game.PacketPlayInEntityAction; ++import net.minecraft.network.protocol.game.PacketPlayInEntityNBTQuery; ++import net.minecraft.network.protocol.game.PacketPlayInFlying; ++import net.minecraft.network.protocol.game.PacketPlayInHeldItemSlot; ++import net.minecraft.network.protocol.game.PacketPlayInItemName; ++import net.minecraft.network.protocol.game.PacketPlayInJigsawGenerate; ++import net.minecraft.network.protocol.game.PacketPlayInKeepAlive; ++import net.minecraft.network.protocol.game.PacketPlayInPickItem; ++import net.minecraft.network.protocol.game.PacketPlayInRecipeDisplayed; ++import net.minecraft.network.protocol.game.PacketPlayInRecipeSettings; ++import net.minecraft.network.protocol.game.PacketPlayInResourcePackStatus; ++import net.minecraft.network.protocol.game.PacketPlayInSetCommandBlock; ++import net.minecraft.network.protocol.game.PacketPlayInSetCommandMinecart; ++import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; ++import net.minecraft.network.protocol.game.PacketPlayInSetJigsaw; ++import net.minecraft.network.protocol.game.PacketPlayInSettings; ++import net.minecraft.network.protocol.game.PacketPlayInSpectate; ++import net.minecraft.network.protocol.game.PacketPlayInSteerVehicle; ++import net.minecraft.network.protocol.game.PacketPlayInStruct; ++import net.minecraft.network.protocol.game.PacketPlayInTabComplete; ++import net.minecraft.network.protocol.game.PacketPlayInTeleportAccept; ++import net.minecraft.network.protocol.game.PacketPlayInTileNBTQuery; ++import net.minecraft.network.protocol.game.PacketPlayInTrSel; ++import net.minecraft.network.protocol.game.PacketPlayInTransaction; ++import net.minecraft.network.protocol.game.PacketPlayInUpdateSign; ++import net.minecraft.network.protocol.game.PacketPlayInUseEntity; ++import net.minecraft.network.protocol.game.PacketPlayInUseItem; ++import net.minecraft.network.protocol.game.PacketPlayInVehicleMove; ++import net.minecraft.network.protocol.game.PacketPlayInWindowClick; ++import net.minecraft.network.protocol.game.PacketPlayOutAbilities; ++import net.minecraft.network.protocol.game.PacketPlayOutAdvancements; ++import net.minecraft.network.protocol.game.PacketPlayOutAnimation; ++import net.minecraft.network.protocol.game.PacketPlayOutAttachEntity; ++import net.minecraft.network.protocol.game.PacketPlayOutAutoRecipe; ++import net.minecraft.network.protocol.game.PacketPlayOutBlockAction; ++import net.minecraft.network.protocol.game.PacketPlayOutBlockBreak; ++import net.minecraft.network.protocol.game.PacketPlayOutBlockBreakAnimation; ++import net.minecraft.network.protocol.game.PacketPlayOutBlockChange; ++import net.minecraft.network.protocol.game.PacketPlayOutBoss; ++import net.minecraft.network.protocol.game.PacketPlayOutCamera; ++import net.minecraft.network.protocol.game.PacketPlayOutChat; ++import net.minecraft.network.protocol.game.PacketPlayOutCloseWindow; ++import net.minecraft.network.protocol.game.PacketPlayOutCollect; ++import net.minecraft.network.protocol.game.PacketPlayOutCombatEvent; ++import net.minecraft.network.protocol.game.PacketPlayOutCommands; ++import net.minecraft.network.protocol.game.PacketPlayOutCustomPayload; ++import net.minecraft.network.protocol.game.PacketPlayOutCustomSoundEffect; ++import net.minecraft.network.protocol.game.PacketPlayOutEntity; ++import net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy; ++import net.minecraft.network.protocol.game.PacketPlayOutEntityEffect; ++import net.minecraft.network.protocol.game.PacketPlayOutEntityEquipment; ++import net.minecraft.network.protocol.game.PacketPlayOutEntityHeadRotation; ++import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; ++import net.minecraft.network.protocol.game.PacketPlayOutEntitySound; ++import net.minecraft.network.protocol.game.PacketPlayOutEntityStatus; ++import net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport; ++import net.minecraft.network.protocol.game.PacketPlayOutEntityVelocity; ++import net.minecraft.network.protocol.game.PacketPlayOutExperience; ++import net.minecraft.network.protocol.game.PacketPlayOutExplosion; ++import net.minecraft.network.protocol.game.PacketPlayOutGameStateChange; ++import net.minecraft.network.protocol.game.PacketPlayOutHeldItemSlot; ++import net.minecraft.network.protocol.game.PacketPlayOutKeepAlive; ++import net.minecraft.network.protocol.game.PacketPlayOutKickDisconnect; ++import net.minecraft.network.protocol.game.PacketPlayOutLightUpdate; ++import net.minecraft.network.protocol.game.PacketPlayOutLogin; ++import net.minecraft.network.protocol.game.PacketPlayOutLookAt; ++import net.minecraft.network.protocol.game.PacketPlayOutMap; ++import net.minecraft.network.protocol.game.PacketPlayOutMapChunk; ++import net.minecraft.network.protocol.game.PacketPlayOutMount; ++import net.minecraft.network.protocol.game.PacketPlayOutMultiBlockChange; ++import net.minecraft.network.protocol.game.PacketPlayOutNBTQuery; ++import net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn; ++import net.minecraft.network.protocol.game.PacketPlayOutNamedSoundEffect; ++import net.minecraft.network.protocol.game.PacketPlayOutOpenBook; ++import net.minecraft.network.protocol.game.PacketPlayOutOpenSignEditor; ++import net.minecraft.network.protocol.game.PacketPlayOutOpenWindow; ++import net.minecraft.network.protocol.game.PacketPlayOutOpenWindowHorse; ++import net.minecraft.network.protocol.game.PacketPlayOutOpenWindowMerchant; ++import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo; ++import net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter; ++import net.minecraft.network.protocol.game.PacketPlayOutPosition; ++import net.minecraft.network.protocol.game.PacketPlayOutRecipeUpdate; ++import net.minecraft.network.protocol.game.PacketPlayOutRecipes; ++import net.minecraft.network.protocol.game.PacketPlayOutRemoveEntityEffect; ++import net.minecraft.network.protocol.game.PacketPlayOutResourcePackSend; ++import net.minecraft.network.protocol.game.PacketPlayOutRespawn; ++import net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective; ++import net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective; ++import net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore; ++import net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam; ++import net.minecraft.network.protocol.game.PacketPlayOutSelectAdvancementTab; ++import net.minecraft.network.protocol.game.PacketPlayOutServerDifficulty; ++import net.minecraft.network.protocol.game.PacketPlayOutSetCooldown; ++import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; ++import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity; ++import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityExperienceOrb; ++import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving; ++import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityPainting; ++import net.minecraft.network.protocol.game.PacketPlayOutSpawnPosition; ++import net.minecraft.network.protocol.game.PacketPlayOutStatistic; ++import net.minecraft.network.protocol.game.PacketPlayOutStopSound; ++import net.minecraft.network.protocol.game.PacketPlayOutTabComplete; ++import net.minecraft.network.protocol.game.PacketPlayOutTags; ++import net.minecraft.network.protocol.game.PacketPlayOutTileEntityData; ++import net.minecraft.network.protocol.game.PacketPlayOutTitle; ++import net.minecraft.network.protocol.game.PacketPlayOutTransaction; ++import net.minecraft.network.protocol.game.PacketPlayOutUnloadChunk; ++import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes; ++import net.minecraft.network.protocol.game.PacketPlayOutUpdateHealth; ++import net.minecraft.network.protocol.game.PacketPlayOutUpdateTime; ++import net.minecraft.network.protocol.game.PacketPlayOutVehicleMove; ++import net.minecraft.network.protocol.game.PacketPlayOutViewCentre; ++import net.minecraft.network.protocol.game.PacketPlayOutViewDistance; ++import net.minecraft.network.protocol.game.PacketPlayOutWindowData; ++import net.minecraft.network.protocol.game.PacketPlayOutWindowItems; ++import net.minecraft.network.protocol.game.PacketPlayOutWorldBorder; ++import net.minecraft.network.protocol.game.PacketPlayOutWorldEvent; ++import net.minecraft.network.protocol.game.PacketPlayOutWorldParticles; ++import net.minecraft.network.protocol.handshake.PacketHandshakingInSetProtocol; ++import net.minecraft.network.protocol.login.PacketLoginInCustomPayload; ++import net.minecraft.network.protocol.login.PacketLoginInEncryptionBegin; ++import net.minecraft.network.protocol.login.PacketLoginInStart; ++import net.minecraft.network.protocol.login.PacketLoginOutCustomPayload; ++import net.minecraft.network.protocol.login.PacketLoginOutDisconnect; ++import net.minecraft.network.protocol.login.PacketLoginOutEncryptionBegin; ++import net.minecraft.network.protocol.login.PacketLoginOutSetCompression; ++import net.minecraft.network.protocol.login.PacketLoginOutSuccess; ++import net.minecraft.network.protocol.status.PacketStatusInPing; ++import net.minecraft.network.protocol.status.PacketStatusInStart; ++import net.minecraft.network.protocol.status.PacketStatusOutPong; ++import net.minecraft.network.protocol.status.PacketStatusOutServerInfo; ++import org.apache.logging.log4j.LogManager; ++ ++public enum EnumProtocol { ++ ++ HANDSHAKING(-1, b().a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a<>()).a(PacketHandshakingInSetProtocol.class, PacketHandshakingInSetProtocol::new))), PLAY(0, b().a(EnumProtocolDirection.CLIENTBOUND, (new EnumProtocol.a<>()).a(PacketPlayOutSpawnEntity.class, PacketPlayOutSpawnEntity::new).a(PacketPlayOutSpawnEntityExperienceOrb.class, PacketPlayOutSpawnEntityExperienceOrb::new).a(PacketPlayOutSpawnEntityLiving.class, PacketPlayOutSpawnEntityLiving::new).a(PacketPlayOutSpawnEntityPainting.class, PacketPlayOutSpawnEntityPainting::new).a(PacketPlayOutNamedEntitySpawn.class, PacketPlayOutNamedEntitySpawn::new).a(PacketPlayOutAnimation.class, PacketPlayOutAnimation::new).a(PacketPlayOutStatistic.class, PacketPlayOutStatistic::new).a(PacketPlayOutBlockBreak.class, PacketPlayOutBlockBreak::new).a(PacketPlayOutBlockBreakAnimation.class, PacketPlayOutBlockBreakAnimation::new).a(PacketPlayOutTileEntityData.class, PacketPlayOutTileEntityData::new).a(PacketPlayOutBlockAction.class, PacketPlayOutBlockAction::new).a(PacketPlayOutBlockChange.class, PacketPlayOutBlockChange::new).a(PacketPlayOutBoss.class, PacketPlayOutBoss::new).a(PacketPlayOutServerDifficulty.class, PacketPlayOutServerDifficulty::new).a(PacketPlayOutChat.class, PacketPlayOutChat::new).a(PacketPlayOutTabComplete.class, PacketPlayOutTabComplete::new).a(PacketPlayOutCommands.class, PacketPlayOutCommands::new).a(PacketPlayOutTransaction.class, PacketPlayOutTransaction::new).a(PacketPlayOutCloseWindow.class, PacketPlayOutCloseWindow::new).a(PacketPlayOutWindowItems.class, PacketPlayOutWindowItems::new).a(PacketPlayOutWindowData.class, PacketPlayOutWindowData::new).a(PacketPlayOutSetSlot.class, PacketPlayOutSetSlot::new).a(PacketPlayOutSetCooldown.class, PacketPlayOutSetCooldown::new).a(PacketPlayOutCustomPayload.class, PacketPlayOutCustomPayload::new).a(PacketPlayOutCustomSoundEffect.class, PacketPlayOutCustomSoundEffect::new).a(PacketPlayOutKickDisconnect.class, PacketPlayOutKickDisconnect::new).a(PacketPlayOutEntityStatus.class, PacketPlayOutEntityStatus::new).a(PacketPlayOutExplosion.class, PacketPlayOutExplosion::new).a(PacketPlayOutUnloadChunk.class, PacketPlayOutUnloadChunk::new).a(PacketPlayOutGameStateChange.class, PacketPlayOutGameStateChange::new).a(PacketPlayOutOpenWindowHorse.class, PacketPlayOutOpenWindowHorse::new).a(PacketPlayOutKeepAlive.class, PacketPlayOutKeepAlive::new).a(PacketPlayOutMapChunk.class, PacketPlayOutMapChunk::new).a(PacketPlayOutWorldEvent.class, PacketPlayOutWorldEvent::new).a(PacketPlayOutWorldParticles.class, PacketPlayOutWorldParticles::new).a(PacketPlayOutLightUpdate.class, PacketPlayOutLightUpdate::new).a(PacketPlayOutLogin.class, PacketPlayOutLogin::new).a(PacketPlayOutMap.class, PacketPlayOutMap::new).a(PacketPlayOutOpenWindowMerchant.class, PacketPlayOutOpenWindowMerchant::new).a(PacketPlayOutEntity.PacketPlayOutRelEntityMove.class, PacketPlayOutEntity.PacketPlayOutRelEntityMove::new).a(PacketPlayOutEntity.PacketPlayOutRelEntityMoveLook.class, PacketPlayOutEntity.PacketPlayOutRelEntityMoveLook::new).a(PacketPlayOutEntity.PacketPlayOutEntityLook.class, PacketPlayOutEntity.PacketPlayOutEntityLook::new).a(PacketPlayOutEntity.class, PacketPlayOutEntity::new).a(PacketPlayOutVehicleMove.class, PacketPlayOutVehicleMove::new).a(PacketPlayOutOpenBook.class, PacketPlayOutOpenBook::new).a(PacketPlayOutOpenWindow.class, PacketPlayOutOpenWindow::new).a(PacketPlayOutOpenSignEditor.class, PacketPlayOutOpenSignEditor::new).a(PacketPlayOutAutoRecipe.class, PacketPlayOutAutoRecipe::new).a(PacketPlayOutAbilities.class, PacketPlayOutAbilities::new).a(PacketPlayOutCombatEvent.class, PacketPlayOutCombatEvent::new).a(PacketPlayOutPlayerInfo.class, PacketPlayOutPlayerInfo::new).a(PacketPlayOutLookAt.class, PacketPlayOutLookAt::new).a(PacketPlayOutPosition.class, PacketPlayOutPosition::new).a(PacketPlayOutRecipes.class, PacketPlayOutRecipes::new).a(PacketPlayOutEntityDestroy.class, PacketPlayOutEntityDestroy::new).a(PacketPlayOutRemoveEntityEffect.class, PacketPlayOutRemoveEntityEffect::new).a(PacketPlayOutResourcePackSend.class, PacketPlayOutResourcePackSend::new).a(PacketPlayOutRespawn.class, PacketPlayOutRespawn::new).a(PacketPlayOutEntityHeadRotation.class, PacketPlayOutEntityHeadRotation::new).a(PacketPlayOutMultiBlockChange.class, PacketPlayOutMultiBlockChange::new).a(PacketPlayOutSelectAdvancementTab.class, PacketPlayOutSelectAdvancementTab::new).a(PacketPlayOutWorldBorder.class, PacketPlayOutWorldBorder::new).a(PacketPlayOutCamera.class, PacketPlayOutCamera::new).a(PacketPlayOutHeldItemSlot.class, PacketPlayOutHeldItemSlot::new).a(PacketPlayOutViewCentre.class, PacketPlayOutViewCentre::new).a(PacketPlayOutViewDistance.class, PacketPlayOutViewDistance::new).a(PacketPlayOutSpawnPosition.class, PacketPlayOutSpawnPosition::new).a(PacketPlayOutScoreboardDisplayObjective.class, PacketPlayOutScoreboardDisplayObjective::new).a(PacketPlayOutEntityMetadata.class, PacketPlayOutEntityMetadata::new).a(PacketPlayOutAttachEntity.class, PacketPlayOutAttachEntity::new).a(PacketPlayOutEntityVelocity.class, PacketPlayOutEntityVelocity::new).a(PacketPlayOutEntityEquipment.class, PacketPlayOutEntityEquipment::new).a(PacketPlayOutExperience.class, PacketPlayOutExperience::new).a(PacketPlayOutUpdateHealth.class, PacketPlayOutUpdateHealth::new).a(PacketPlayOutScoreboardObjective.class, PacketPlayOutScoreboardObjective::new).a(PacketPlayOutMount.class, PacketPlayOutMount::new).a(PacketPlayOutScoreboardTeam.class, PacketPlayOutScoreboardTeam::new).a(PacketPlayOutScoreboardScore.class, PacketPlayOutScoreboardScore::new).a(PacketPlayOutUpdateTime.class, PacketPlayOutUpdateTime::new).a(PacketPlayOutTitle.class, PacketPlayOutTitle::new).a(PacketPlayOutEntitySound.class, PacketPlayOutEntitySound::new).a(PacketPlayOutNamedSoundEffect.class, PacketPlayOutNamedSoundEffect::new).a(PacketPlayOutStopSound.class, PacketPlayOutStopSound::new).a(PacketPlayOutPlayerListHeaderFooter.class, PacketPlayOutPlayerListHeaderFooter::new).a(PacketPlayOutNBTQuery.class, PacketPlayOutNBTQuery::new).a(PacketPlayOutCollect.class, PacketPlayOutCollect::new).a(PacketPlayOutEntityTeleport.class, PacketPlayOutEntityTeleport::new).a(PacketPlayOutAdvancements.class, PacketPlayOutAdvancements::new).a(PacketPlayOutUpdateAttributes.class, PacketPlayOutUpdateAttributes::new).a(PacketPlayOutEntityEffect.class, PacketPlayOutEntityEffect::new).a(PacketPlayOutRecipeUpdate.class, PacketPlayOutRecipeUpdate::new).a(PacketPlayOutTags.class, PacketPlayOutTags::new)).a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a<>()).a(PacketPlayInTeleportAccept.class, PacketPlayInTeleportAccept::new).a(PacketPlayInTileNBTQuery.class, PacketPlayInTileNBTQuery::new).a(PacketPlayInDifficultyChange.class, PacketPlayInDifficultyChange::new).a(PacketPlayInChat.class, PacketPlayInChat::new).a(PacketPlayInClientCommand.class, PacketPlayInClientCommand::new).a(PacketPlayInSettings.class, PacketPlayInSettings::new).a(PacketPlayInTabComplete.class, PacketPlayInTabComplete::new).a(PacketPlayInTransaction.class, PacketPlayInTransaction::new).a(PacketPlayInEnchantItem.class, PacketPlayInEnchantItem::new).a(PacketPlayInWindowClick.class, PacketPlayInWindowClick::new).a(PacketPlayInCloseWindow.class, PacketPlayInCloseWindow::new).a(PacketPlayInCustomPayload.class, PacketPlayInCustomPayload::new).a(PacketPlayInBEdit.class, PacketPlayInBEdit::new).a(PacketPlayInEntityNBTQuery.class, PacketPlayInEntityNBTQuery::new).a(PacketPlayInUseEntity.class, PacketPlayInUseEntity::new).a(PacketPlayInJigsawGenerate.class, PacketPlayInJigsawGenerate::new).a(PacketPlayInKeepAlive.class, PacketPlayInKeepAlive::new).a(PacketPlayInDifficultyLock.class, PacketPlayInDifficultyLock::new).a(PacketPlayInFlying.PacketPlayInPosition.class, PacketPlayInFlying.PacketPlayInPosition::new).a(PacketPlayInFlying.PacketPlayInPositionLook.class, PacketPlayInFlying.PacketPlayInPositionLook::new).a(PacketPlayInFlying.PacketPlayInLook.class, PacketPlayInFlying.PacketPlayInLook::new).a(PacketPlayInFlying.class, PacketPlayInFlying::new).a(PacketPlayInVehicleMove.class, PacketPlayInVehicleMove::new).a(PacketPlayInBoatMove.class, PacketPlayInBoatMove::new).a(PacketPlayInPickItem.class, PacketPlayInPickItem::new).a(PacketPlayInAutoRecipe.class, PacketPlayInAutoRecipe::new).a(PacketPlayInAbilities.class, PacketPlayInAbilities::new).a(PacketPlayInBlockDig.class, PacketPlayInBlockDig::new).a(PacketPlayInEntityAction.class, PacketPlayInEntityAction::new).a(PacketPlayInSteerVehicle.class, PacketPlayInSteerVehicle::new).a(PacketPlayInRecipeSettings.class, PacketPlayInRecipeSettings::new).a(PacketPlayInRecipeDisplayed.class, PacketPlayInRecipeDisplayed::new).a(PacketPlayInItemName.class, PacketPlayInItemName::new).a(PacketPlayInResourcePackStatus.class, PacketPlayInResourcePackStatus::new).a(PacketPlayInAdvancements.class, PacketPlayInAdvancements::new).a(PacketPlayInTrSel.class, PacketPlayInTrSel::new).a(PacketPlayInBeacon.class, PacketPlayInBeacon::new).a(PacketPlayInHeldItemSlot.class, PacketPlayInHeldItemSlot::new).a(PacketPlayInSetCommandBlock.class, PacketPlayInSetCommandBlock::new).a(PacketPlayInSetCommandMinecart.class, PacketPlayInSetCommandMinecart::new).a(PacketPlayInSetCreativeSlot.class, PacketPlayInSetCreativeSlot::new).a(PacketPlayInSetJigsaw.class, PacketPlayInSetJigsaw::new).a(PacketPlayInStruct.class, PacketPlayInStruct::new).a(PacketPlayInUpdateSign.class, PacketPlayInUpdateSign::new).a(PacketPlayInArmAnimation.class, PacketPlayInArmAnimation::new).a(PacketPlayInSpectate.class, PacketPlayInSpectate::new).a(PacketPlayInUseItem.class, PacketPlayInUseItem::new).a(PacketPlayInBlockPlace.class, PacketPlayInBlockPlace::new))), STATUS(1, b().a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a<>()).a(PacketStatusInStart.class, PacketStatusInStart::new).a(PacketStatusInPing.class, PacketStatusInPing::new)).a(EnumProtocolDirection.CLIENTBOUND, (new EnumProtocol.a<>()).a(PacketStatusOutServerInfo.class, PacketStatusOutServerInfo::new).a(PacketStatusOutPong.class, PacketStatusOutPong::new))), LOGIN(2, b().a(EnumProtocolDirection.CLIENTBOUND, (new EnumProtocol.a<>()).a(PacketLoginOutDisconnect.class, PacketLoginOutDisconnect::new).a(PacketLoginOutEncryptionBegin.class, PacketLoginOutEncryptionBegin::new).a(PacketLoginOutSuccess.class, PacketLoginOutSuccess::new).a(PacketLoginOutSetCompression.class, PacketLoginOutSetCompression::new).a(PacketLoginOutCustomPayload.class, PacketLoginOutCustomPayload::new)).a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a<>()).a(PacketLoginInStart.class, PacketLoginInStart::new).a(PacketLoginInEncryptionBegin.class, PacketLoginInEncryptionBegin::new).a(PacketLoginInCustomPayload.class, PacketLoginInCustomPayload::new))); ++ ++ private static final EnumProtocol[] e = new EnumProtocol[4]; ++ private static final Map>, EnumProtocol> f = Maps.newHashMap(); ++ private final int g; ++ private final Map> h; ++ ++ private static EnumProtocol.b b() { ++ return new EnumProtocol.b(); ++ } ++ ++ private EnumProtocol(int i, EnumProtocol.b enumprotocol_b) { ++ this.g = i; ++ this.h = enumprotocol_b.a; ++ } ++ ++ @Nullable ++ public Integer a(EnumProtocolDirection enumprotocoldirection, Packet packet) { ++ return ((EnumProtocol.a) this.h.get(enumprotocoldirection)).a(packet.getClass()); ++ } ++ ++ @Nullable ++ public Packet a(EnumProtocolDirection enumprotocoldirection, int i) { ++ return ((EnumProtocol.a) this.h.get(enumprotocoldirection)).a(i); ++ } ++ ++ public int a() { ++ return this.g; ++ } ++ ++ @Nullable ++ public static EnumProtocol a(int i) { ++ return i >= -1 && i <= 2 ? EnumProtocol.e[i - -1] : null; ++ } ++ ++ public static EnumProtocol a(Packet packet) { ++ return (EnumProtocol) EnumProtocol.f.get(packet.getClass()); ++ } ++ ++ static { ++ EnumProtocol[] aenumprotocol = values(); ++ int i = aenumprotocol.length; ++ ++ for (int j = 0; j < i; ++j) { ++ EnumProtocol enumprotocol = aenumprotocol[j]; ++ int k = enumprotocol.a(); ++ ++ if (k < -1 || k > 2) { ++ throw new Error("Invalid protocol ID " + Integer.toString(k)); ++ } ++ ++ EnumProtocol.e[k - -1] = enumprotocol; ++ enumprotocol.h.forEach((enumprotocoldirection, enumprotocol_a) -> { ++ enumprotocol_a.a().forEach((oclass) -> { ++ if (EnumProtocol.f.containsKey(oclass) && EnumProtocol.f.get(oclass) != enumprotocol) { ++ throw new IllegalStateException("Packet " + oclass + " is already assigned to protocol " + EnumProtocol.f.get(oclass) + " - can't reassign to " + enumprotocol); ++ } else { ++ EnumProtocol.f.put(oclass, enumprotocol); ++ } ++ }); ++ }); ++ } ++ ++ } ++ ++ static class b { ++ ++ private final Map> a; ++ ++ private b() { ++ this.a = Maps.newEnumMap(EnumProtocolDirection.class); ++ } ++ ++ public EnumProtocol.b a(EnumProtocolDirection enumprotocoldirection, EnumProtocol.a enumprotocol_a) { ++ this.a.put(enumprotocoldirection, enumprotocol_a); ++ return this; ++ } ++ } ++ ++ static class a { ++ ++ private final Object2IntMap>> a; ++ private final List>> b; ++ ++ private a() { ++ this.a = (Object2IntMap) SystemUtils.a((Object) (new Object2IntOpenHashMap()), (object2intopenhashmap) -> { ++ object2intopenhashmap.defaultReturnValue(-1); ++ }); ++ this.b = Lists.newArrayList(); ++ } ++ ++ public

> EnumProtocol.a a(Class

oclass, Supplier

supplier) { ++ int i = this.b.size(); ++ int j = this.a.put(oclass, i); ++ ++ if (j != -1) { ++ String s = "Packet " + oclass + " is already registered to ID " + j; ++ ++ LogManager.getLogger().fatal(s); ++ throw new IllegalArgumentException(s); ++ } else { ++ this.b.add(supplier); ++ return this; ++ } ++ } ++ ++ @Nullable ++ public Integer a(Class oclass) { ++ int i = this.a.getInt(oclass); ++ ++ return i == -1 ? null : i; ++ } ++ ++ @Nullable ++ public Packet a(int i) { ++ Supplier> supplier = (Supplier) this.b.get(i); ++ ++ return supplier != null ? (Packet) supplier.get() : null; ++ } ++ ++ public Iterable>> a() { ++ return Iterables.unmodifiableIterable(this.a.keySet()); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/network/PacketCompressor.java b/src/main/java/net/minecraft/network/PacketCompressor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..45b9d3d3c84d11e7f27f699506a1036dff9fdc53 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/PacketCompressor.java +@@ -0,0 +1,48 @@ ++package net.minecraft.network; ++ ++import io.netty.buffer.ByteBuf; ++import io.netty.channel.ChannelHandlerContext; ++import io.netty.handler.codec.MessageToByteEncoder; ++import java.util.zip.Deflater; ++ ++public class PacketCompressor extends MessageToByteEncoder { ++ ++ private final byte[] a = new byte[8192]; ++ private final Deflater b; ++ private int c; ++ ++ public PacketCompressor(int i) { ++ this.c = i; ++ this.b = new Deflater(); ++ } ++ ++ protected void encode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, ByteBuf bytebuf1) throws Exception { ++ int i = bytebuf.readableBytes(); ++ PacketDataSerializer packetdataserializer = new PacketDataSerializer(bytebuf1); ++ ++ if (i < this.c) { ++ packetdataserializer.d(0); ++ packetdataserializer.writeBytes(bytebuf); ++ } else { ++ byte[] abyte = new byte[i]; ++ ++ bytebuf.readBytes(abyte); ++ packetdataserializer.d(abyte.length); ++ this.b.setInput(abyte, 0, i); ++ this.b.finish(); ++ ++ while (!this.b.finished()) { ++ int j = this.b.deflate(this.a); ++ ++ packetdataserializer.writeBytes(this.a, 0, j); ++ } ++ ++ this.b.reset(); ++ } ++ ++ } ++ ++ public void a(int i) { ++ this.c = i; ++ } ++} +diff --git a/src/main/java/net/minecraft/network/PacketDecompressor.java b/src/main/java/net/minecraft/network/PacketDecompressor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1932ca55dad37ca773f215eaec23164533d509d3 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/PacketDecompressor.java +@@ -0,0 +1,54 @@ ++package net.minecraft.network; ++ ++import io.netty.buffer.ByteBuf; ++import io.netty.buffer.Unpooled; ++import io.netty.channel.ChannelHandlerContext; ++import io.netty.handler.codec.ByteToMessageDecoder; ++import io.netty.handler.codec.DecoderException; ++import java.util.List; ++import java.util.zip.Inflater; ++ ++public class PacketDecompressor extends ByteToMessageDecoder { ++ ++ private final Inflater a; ++ private int b; ++ ++ public PacketDecompressor(int i) { ++ this.b = i; ++ this.a = new Inflater(); ++ } ++ ++ protected void decode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, List list) throws Exception { ++ if (bytebuf.readableBytes() != 0) { ++ PacketDataSerializer packetdataserializer = new PacketDataSerializer(bytebuf); ++ int i = packetdataserializer.i(); ++ ++ if (i == 0) { ++ list.add(packetdataserializer.readBytes(packetdataserializer.readableBytes())); ++ } else { ++ if (i < this.b) { ++ throw new DecoderException("Badly compressed packet - size of " + i + " is below server threshold of " + this.b); ++ } ++ ++ if (i > 2097152) { ++ throw new DecoderException("Badly compressed packet - size of " + i + " is larger than protocol maximum of " + 2097152); ++ } ++ ++ byte[] abyte = new byte[packetdataserializer.readableBytes()]; ++ ++ packetdataserializer.readBytes(abyte); ++ this.a.setInput(abyte); ++ byte[] abyte1 = new byte[i]; ++ ++ this.a.inflate(abyte1); ++ list.add(Unpooled.wrappedBuffer(abyte1)); ++ this.a.reset(); ++ } ++ ++ } ++ } ++ ++ public void a(int i) { ++ this.b = i; ++ } ++} +diff --git a/src/main/java/net/minecraft/network/PacketDecrypter.java b/src/main/java/net/minecraft/network/PacketDecrypter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c8aa02b288d67efe1f9e20e038248a4f032f92dd +--- /dev/null ++++ b/src/main/java/net/minecraft/network/PacketDecrypter.java +@@ -0,0 +1,20 @@ ++package net.minecraft.network; ++ ++import io.netty.buffer.ByteBuf; ++import io.netty.channel.ChannelHandlerContext; ++import io.netty.handler.codec.MessageToMessageDecoder; ++import java.util.List; ++import javax.crypto.Cipher; ++ ++public class PacketDecrypter extends MessageToMessageDecoder { ++ ++ private final PacketEncryptionHandler a; ++ ++ public PacketDecrypter(Cipher cipher) { ++ this.a = new PacketEncryptionHandler(cipher); ++ } ++ ++ protected void decode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, List list) throws Exception { ++ list.add(this.a.a(channelhandlercontext, bytebuf)); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..06098698e4adc31aa96f9592975e441f965b5558 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/PacketEncoder.java +@@ -0,0 +1,56 @@ ++package net.minecraft.network; ++ ++import io.netty.buffer.ByteBuf; ++import io.netty.channel.ChannelHandlerContext; ++import io.netty.handler.codec.MessageToByteEncoder; ++import java.io.IOException; ++import net.minecraft.network.protocol.EnumProtocolDirection; ++import net.minecraft.network.protocol.Packet; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++import org.apache.logging.log4j.Marker; ++import org.apache.logging.log4j.MarkerManager; ++ ++public class PacketEncoder extends MessageToByteEncoder> { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private static final Marker b = MarkerManager.getMarker("PACKET_SENT", NetworkManager.b); ++ private final EnumProtocolDirection c; ++ ++ public PacketEncoder(EnumProtocolDirection enumprotocoldirection) { ++ this.c = enumprotocoldirection; ++ } ++ ++ protected void encode(ChannelHandlerContext channelhandlercontext, Packet packet, ByteBuf bytebuf) throws Exception { ++ EnumProtocol enumprotocol = (EnumProtocol) channelhandlercontext.channel().attr(NetworkManager.c).get(); ++ ++ if (enumprotocol == null) { ++ throw new RuntimeException("ConnectionProtocol unknown: " + packet); ++ } else { ++ Integer integer = enumprotocol.a(this.c, packet); ++ ++ if (PacketEncoder.LOGGER.isDebugEnabled()) { ++ PacketEncoder.LOGGER.debug(PacketEncoder.b, "OUT: [{}:{}] {}", channelhandlercontext.channel().attr(NetworkManager.c).get(), integer, packet.getClass().getName()); ++ } ++ ++ if (integer == null) { ++ throw new IOException("Can't serialize unregistered packet"); ++ } else { ++ PacketDataSerializer packetdataserializer = new PacketDataSerializer(bytebuf); ++ ++ packetdataserializer.d(integer); ++ ++ try { ++ packet.b(packetdataserializer); ++ } catch (Throwable throwable) { ++ PacketEncoder.LOGGER.error(throwable); ++ if (packet.a()) { ++ throw new SkipEncodeException(throwable); ++ } else { ++ throw throwable; ++ } ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/network/PacketEncrypter.java b/src/main/java/net/minecraft/network/PacketEncrypter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5759f91d5e9dc52b16c8955b8d318da2b53c7af4 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/PacketEncrypter.java +@@ -0,0 +1,19 @@ ++package net.minecraft.network; ++ ++import io.netty.buffer.ByteBuf; ++import io.netty.channel.ChannelHandlerContext; ++import io.netty.handler.codec.MessageToByteEncoder; ++import javax.crypto.Cipher; ++ ++public class PacketEncrypter extends MessageToByteEncoder { ++ ++ private final PacketEncryptionHandler a; ++ ++ public PacketEncrypter(Cipher cipher) { ++ this.a = new PacketEncryptionHandler(cipher); ++ } ++ ++ protected void encode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, ByteBuf bytebuf1) throws Exception { ++ this.a.a(bytebuf, bytebuf1); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/PacketSplitter.java b/src/main/java/net/minecraft/network/PacketSplitter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2c7de7ab6da2106394ec668cd7cb9be1f8dabeb3 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/PacketSplitter.java +@@ -0,0 +1,47 @@ ++package net.minecraft.network; ++ ++import io.netty.buffer.ByteBuf; ++import io.netty.buffer.Unpooled; ++import io.netty.channel.ChannelHandlerContext; ++import io.netty.handler.codec.ByteToMessageDecoder; ++import io.netty.handler.codec.CorruptedFrameException; ++import java.util.List; ++ ++public class PacketSplitter extends ByteToMessageDecoder { ++ ++ public PacketSplitter() {} ++ ++ protected void decode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, List list) throws Exception { ++ bytebuf.markReaderIndex(); ++ byte[] abyte = new byte[3]; ++ ++ for (int i = 0; i < abyte.length; ++i) { ++ if (!bytebuf.isReadable()) { ++ bytebuf.resetReaderIndex(); ++ return; ++ } ++ ++ abyte[i] = bytebuf.readByte(); ++ if (abyte[i] >= 0) { ++ PacketDataSerializer packetdataserializer = new PacketDataSerializer(Unpooled.wrappedBuffer(abyte)); ++ ++ try { ++ int j = packetdataserializer.i(); ++ ++ if (bytebuf.readableBytes() >= j) { ++ list.add(bytebuf.readBytes(j)); ++ return; ++ } ++ ++ bytebuf.resetReaderIndex(); ++ } finally { ++ packetdataserializer.release(); ++ } ++ ++ return; ++ } ++ } ++ ++ throw new CorruptedFrameException("length wider than 21-bit"); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/chat/ChatComponentUtils.java b/src/main/java/net/minecraft/network/chat/ChatComponentUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b00e5d811ddfa12937f57bac4debb2fdd057d6e1 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/chat/ChatComponentUtils.java +@@ -0,0 +1,114 @@ ++package net.minecraft.network.chat; ++ ++import com.google.common.collect.Lists; ++import com.mojang.authlib.GameProfile; ++import com.mojang.brigadier.Message; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.List; ++import java.util.function.Function; ++import javax.annotation.Nullable; ++import net.minecraft.EnumChatFormat; ++import net.minecraft.commands.CommandListenerWrapper; ++import net.minecraft.world.entity.Entity; ++ ++public class ChatComponentUtils { ++ ++ public static IChatMutableComponent a(IChatMutableComponent ichatmutablecomponent, ChatModifier chatmodifier) { ++ if (chatmodifier.g()) { ++ return ichatmutablecomponent; ++ } else { ++ ChatModifier chatmodifier1 = ichatmutablecomponent.getChatModifier(); ++ ++ return chatmodifier1.g() ? ichatmutablecomponent.setChatModifier(chatmodifier) : (chatmodifier1.equals(chatmodifier) ? ichatmutablecomponent : ichatmutablecomponent.setChatModifier(chatmodifier1.setChatModifier(chatmodifier))); ++ } ++ } ++ ++ public static IChatMutableComponent filterForDisplay(@Nullable CommandListenerWrapper commandlistenerwrapper, IChatBaseComponent ichatbasecomponent, @Nullable Entity entity, int i) throws CommandSyntaxException { ++ if (i > 100) { ++ return ichatbasecomponent.mutableCopy(); ++ } else { ++ IChatMutableComponent ichatmutablecomponent = ichatbasecomponent instanceof ChatComponentContextual ? ((ChatComponentContextual) ichatbasecomponent).a(commandlistenerwrapper, entity, i + 1) : ichatbasecomponent.g(); ++ Iterator iterator = ichatbasecomponent.getSiblings().iterator(); ++ ++ while (iterator.hasNext()) { ++ IChatBaseComponent ichatbasecomponent1 = (IChatBaseComponent) iterator.next(); ++ ++ ichatmutablecomponent.addSibling(filterForDisplay(commandlistenerwrapper, ichatbasecomponent1, entity, i + 1)); ++ } ++ ++ return ichatmutablecomponent.c(a(commandlistenerwrapper, ichatbasecomponent.getChatModifier(), entity, i)); ++ } ++ } ++ ++ private static ChatModifier a(@Nullable CommandListenerWrapper commandlistenerwrapper, ChatModifier chatmodifier, @Nullable Entity entity, int i) throws CommandSyntaxException { ++ ChatHoverable chathoverable = chatmodifier.getHoverEvent(); ++ ++ if (chathoverable != null) { ++ IChatBaseComponent ichatbasecomponent = (IChatBaseComponent) chathoverable.a(ChatHoverable.EnumHoverAction.SHOW_TEXT); ++ ++ if (ichatbasecomponent != null) { ++ ChatHoverable chathoverable1 = new ChatHoverable(ChatHoverable.EnumHoverAction.SHOW_TEXT, filterForDisplay(commandlistenerwrapper, ichatbasecomponent, entity, i + 1)); ++ ++ return chatmodifier.setChatHoverable(chathoverable1); ++ } ++ } ++ ++ return chatmodifier; ++ } ++ ++ public static IChatBaseComponent a(GameProfile gameprofile) { ++ return gameprofile.getName() != null ? new ChatComponentText(gameprofile.getName()) : (gameprofile.getId() != null ? new ChatComponentText(gameprofile.getId().toString()) : new ChatComponentText("(unknown)")); ++ } ++ ++ public static IChatBaseComponent a(Collection collection) { ++ return a(collection, (s) -> { ++ return (new ChatComponentText(s)).a(EnumChatFormat.GREEN); ++ }); ++ } ++ ++ public static > IChatBaseComponent a(Collection collection, Function function) { ++ if (collection.isEmpty()) { ++ return ChatComponentText.d; ++ } else if (collection.size() == 1) { ++ return (IChatBaseComponent) function.apply(collection.iterator().next()); ++ } else { ++ List list = Lists.newArrayList(collection); ++ ++ list.sort(Comparable::compareTo); ++ return b(list, function); ++ } ++ } ++ ++ public static IChatMutableComponent b(Collection collection, Function function) { ++ if (collection.isEmpty()) { ++ return new ChatComponentText(""); ++ } else if (collection.size() == 1) { ++ return ((IChatBaseComponent) function.apply(collection.iterator().next())).mutableCopy(); ++ } else { ++ ChatComponentText chatcomponenttext = new ChatComponentText(""); ++ boolean flag = true; ++ ++ for (Iterator iterator = collection.iterator(); iterator.hasNext(); flag = false) { ++ T t0 = iterator.next(); ++ ++ if (!flag) { ++ chatcomponenttext.addSibling((new ChatComponentText(", ")).a(EnumChatFormat.GRAY)); ++ } ++ ++ chatcomponenttext.addSibling((IChatBaseComponent) function.apply(t0)); ++ } ++ ++ return chatcomponenttext; ++ } ++ } ++ ++ public static IChatMutableComponent a(IChatBaseComponent ichatbasecomponent) { ++ return new ChatMessage("chat.square_brackets", new Object[]{ichatbasecomponent}); ++ } ++ ++ public static IChatBaseComponent a(Message message) { ++ return (IChatBaseComponent) (message instanceof IChatBaseComponent ? (IChatBaseComponent) message : new ChatComponentText(message.getString())); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cd4493a023748264748d4e892815f14d8a7bd7f6 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/Packet.java +@@ -0,0 +1,18 @@ ++package net.minecraft.network.protocol; ++ ++import java.io.IOException; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.PacketListener; ++ ++public interface Packet { ++ ++ void a(PacketDataSerializer packetdataserializer) throws IOException; ++ ++ void b(PacketDataSerializer packetdataserializer) throws IOException; ++ ++ void a(T t0); ++ ++ default boolean a() { ++ return false; ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInBEdit.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInBEdit.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d748e07f8870023e74796910a457d58ee0361ca6 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInBEdit.java +@@ -0,0 +1,45 @@ ++package net.minecraft.network.protocol.game; ++ ++import java.io.IOException; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.world.item.ItemStack; ++ ++public class PacketPlayInBEdit implements Packet { ++ ++ private ItemStack a; ++ private boolean b; ++ private int c; ++ ++ public PacketPlayInBEdit() {} ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.n(); ++ this.b = packetdataserializer.readBoolean(); ++ this.c = packetdataserializer.i(); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.a(this.a); ++ packetdataserializer.writeBoolean(this.b); ++ packetdataserializer.d(this.c); ++ } ++ ++ public void a(PacketListenerPlayIn packetlistenerplayin) { ++ packetlistenerplayin.a(this); ++ } ++ ++ public ItemStack b() { ++ return this.a; ++ } ++ ++ public boolean c() { ++ return this.b; ++ } ++ ++ public int d() { ++ return this.c; ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInHeldItemSlot.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInHeldItemSlot.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d68f3e6b35f0af846c8a66710c5752508c095179 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInHeldItemSlot.java +@@ -0,0 +1,30 @@ ++package net.minecraft.network.protocol.game; ++ ++import java.io.IOException; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++ ++public class PacketPlayInHeldItemSlot implements Packet { ++ ++ private int itemInHandIndex; ++ ++ public PacketPlayInHeldItemSlot() {} ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.itemInHandIndex = packetdataserializer.readShort(); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.writeShort(this.itemInHandIndex); ++ } ++ ++ public void a(PacketListenerPlayIn packetlistenerplayin) { ++ packetlistenerplayin.a(this); ++ } ++ ++ public int b() { ++ return this.itemInHandIndex; ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInSettings.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInSettings.java +new file mode 100644 +index 0000000000000000000000000000000000000000..90842b27f64afcdd8eb7d0e52df8cfcb418b5b5a +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInSettings.java +@@ -0,0 +1,59 @@ ++package net.minecraft.network.protocol.game; ++ ++import java.io.IOException; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.world.entity.EnumMainHand; ++import net.minecraft.world.entity.player.EnumChatVisibility; ++ ++public class PacketPlayInSettings implements Packet { ++ ++ public String locale; ++ public int viewDistance; ++ private EnumChatVisibility c; ++ private boolean d; ++ private int e; ++ private EnumMainHand f; ++ ++ public PacketPlayInSettings() {} ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.locale = packetdataserializer.e(16); ++ this.viewDistance = packetdataserializer.readByte(); ++ this.c = (EnumChatVisibility) packetdataserializer.a(EnumChatVisibility.class); ++ this.d = packetdataserializer.readBoolean(); ++ this.e = packetdataserializer.readUnsignedByte(); ++ this.f = (EnumMainHand) packetdataserializer.a(EnumMainHand.class); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.a(this.locale); ++ packetdataserializer.writeByte(this.viewDistance); ++ packetdataserializer.a((Enum) this.c); ++ packetdataserializer.writeBoolean(this.d); ++ packetdataserializer.writeByte(this.e); ++ packetdataserializer.a((Enum) this.f); ++ } ++ ++ public void a(PacketListenerPlayIn packetlistenerplayin) { ++ packetlistenerplayin.a(this); ++ } ++ ++ public EnumChatVisibility d() { ++ return this.c; ++ } ++ ++ public boolean e() { ++ return this.d; ++ } ++ ++ public int f() { ++ return this.e; ++ } ++ ++ public EnumMainHand getMainHand() { ++ return this.f; ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInTabComplete.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInTabComplete.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e201e4efd4ecc65ec3c38528a4ec5336e2d51ab2 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInTabComplete.java +@@ -0,0 +1,37 @@ ++package net.minecraft.network.protocol.game; ++ ++import java.io.IOException; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++ ++public class PacketPlayInTabComplete implements Packet { ++ ++ private int a; ++ private String b; ++ ++ public PacketPlayInTabComplete() {} ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.i(); ++ this.b = packetdataserializer.e(32500); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.d(this.a); ++ packetdataserializer.a(this.b, 32500); ++ } ++ ++ public void a(PacketListenerPlayIn packetlistenerplayin) { ++ packetlistenerplayin.a(this); ++ } ++ ++ public int b() { ++ return this.a; ++ } ++ ++ public String c() { ++ return this.b; ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInUseEntity.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInUseEntity.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9f3f8568ef9484ba226deaa6429f819c325b7a26 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInUseEntity.java +@@ -0,0 +1,86 @@ ++package net.minecraft.network.protocol.game; ++ ++import java.io.IOException; ++import javax.annotation.Nullable; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.World; ++import net.minecraft.world.phys.Vec3D; ++ ++public class PacketPlayInUseEntity implements Packet { ++ ++ private int a; ++ private PacketPlayInUseEntity.EnumEntityUseAction action; ++ private Vec3D c; ++ private EnumHand d; ++ private boolean e; ++ ++ public PacketPlayInUseEntity() {} ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.i(); ++ this.action = (PacketPlayInUseEntity.EnumEntityUseAction) packetdataserializer.a(PacketPlayInUseEntity.EnumEntityUseAction.class); ++ if (this.action == PacketPlayInUseEntity.EnumEntityUseAction.INTERACT_AT) { ++ this.c = new Vec3D((double) packetdataserializer.readFloat(), (double) packetdataserializer.readFloat(), (double) packetdataserializer.readFloat()); ++ } ++ ++ if (this.action == PacketPlayInUseEntity.EnumEntityUseAction.INTERACT || this.action == PacketPlayInUseEntity.EnumEntityUseAction.INTERACT_AT) { ++ this.d = (EnumHand) packetdataserializer.a(EnumHand.class); ++ } ++ ++ this.e = packetdataserializer.readBoolean(); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.d(this.a); ++ packetdataserializer.a((Enum) this.action); ++ if (this.action == PacketPlayInUseEntity.EnumEntityUseAction.INTERACT_AT) { ++ packetdataserializer.writeFloat((float) this.c.x); ++ packetdataserializer.writeFloat((float) this.c.y); ++ packetdataserializer.writeFloat((float) this.c.z); ++ } ++ ++ if (this.action == PacketPlayInUseEntity.EnumEntityUseAction.INTERACT || this.action == PacketPlayInUseEntity.EnumEntityUseAction.INTERACT_AT) { ++ packetdataserializer.a((Enum) this.d); ++ } ++ ++ packetdataserializer.writeBoolean(this.e); ++ } ++ ++ public void a(PacketListenerPlayIn packetlistenerplayin) { ++ packetlistenerplayin.a(this); ++ } ++ ++ @Nullable ++ public Entity a(World world) { ++ return world.getEntity(this.a); ++ } ++ ++ public PacketPlayInUseEntity.EnumEntityUseAction b() { ++ return this.action; ++ } ++ ++ @Nullable ++ public EnumHand c() { ++ return this.d; ++ } ++ ++ public Vec3D d() { ++ return this.c; ++ } ++ ++ public boolean e() { ++ return this.e; ++ } ++ ++ public static enum EnumEntityUseAction { ++ ++ INTERACT, ATTACK, INTERACT_AT; ++ ++ private EnumEntityUseAction() {} ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutEntity.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutEntity.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e80429368afced0299d9f41b97251cd6c64b1759 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutEntity.java +@@ -0,0 +1,159 @@ ++package net.minecraft.network.protocol.game; ++ ++import java.io.IOException; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.phys.Vec3D; ++ ++public class PacketPlayOutEntity implements Packet { ++ ++ protected int a; ++ protected short b; ++ protected short c; ++ protected short d; ++ protected byte e; ++ protected byte f; ++ protected boolean g; ++ protected boolean h; ++ protected boolean i; ++ ++ public static long a(double d0) { ++ return MathHelper.d(d0 * 4096.0D); ++ } ++ ++ public static Vec3D a(long i, long j, long k) { ++ return (new Vec3D((double) i, (double) j, (double) k)).a(2.44140625E-4D); ++ } ++ ++ public PacketPlayOutEntity() {} ++ ++ public PacketPlayOutEntity(int i) { ++ this.a = i; ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.i(); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.d(this.a); ++ } ++ ++ public void a(PacketListenerPlayOut packetlistenerplayout) { ++ packetlistenerplayout.a(this); ++ } ++ ++ public String toString() { ++ return "Entity_" + super.toString(); ++ } ++ ++ public static class PacketPlayOutEntityLook extends PacketPlayOutEntity { ++ ++ public PacketPlayOutEntityLook() { ++ this.h = true; ++ } ++ ++ public PacketPlayOutEntityLook(int i, byte b0, byte b1, boolean flag) { ++ super(i); ++ this.e = b0; ++ this.f = b1; ++ this.h = true; ++ this.g = flag; ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ super.a(packetdataserializer); ++ this.e = packetdataserializer.readByte(); ++ this.f = packetdataserializer.readByte(); ++ this.g = packetdataserializer.readBoolean(); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ super.b(packetdataserializer); ++ packetdataserializer.writeByte(this.e); ++ packetdataserializer.writeByte(this.f); ++ packetdataserializer.writeBoolean(this.g); ++ } ++ } ++ ++ public static class PacketPlayOutRelEntityMove extends PacketPlayOutEntity { ++ ++ public PacketPlayOutRelEntityMove() { ++ this.i = true; ++ } ++ ++ public PacketPlayOutRelEntityMove(int i, short short0, short short1, short short2, boolean flag) { ++ super(i); ++ this.b = short0; ++ this.c = short1; ++ this.d = short2; ++ this.g = flag; ++ this.i = true; ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ super.a(packetdataserializer); ++ this.b = packetdataserializer.readShort(); ++ this.c = packetdataserializer.readShort(); ++ this.d = packetdataserializer.readShort(); ++ this.g = packetdataserializer.readBoolean(); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ super.b(packetdataserializer); ++ packetdataserializer.writeShort(this.b); ++ packetdataserializer.writeShort(this.c); ++ packetdataserializer.writeShort(this.d); ++ packetdataserializer.writeBoolean(this.g); ++ } ++ } ++ ++ public static class PacketPlayOutRelEntityMoveLook extends PacketPlayOutEntity { ++ ++ public PacketPlayOutRelEntityMoveLook() { ++ this.h = true; ++ this.i = true; ++ } ++ ++ public PacketPlayOutRelEntityMoveLook(int i, short short0, short short1, short short2, byte b0, byte b1, boolean flag) { ++ super(i); ++ this.b = short0; ++ this.c = short1; ++ this.d = short2; ++ this.e = b0; ++ this.f = b1; ++ this.g = flag; ++ this.h = true; ++ this.i = true; ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ super.a(packetdataserializer); ++ this.b = packetdataserializer.readShort(); ++ this.c = packetdataserializer.readShort(); ++ this.d = packetdataserializer.readShort(); ++ this.e = packetdataserializer.readByte(); ++ this.f = packetdataserializer.readByte(); ++ this.g = packetdataserializer.readBoolean(); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ super.b(packetdataserializer); ++ packetdataserializer.writeShort(this.b); ++ packetdataserializer.writeShort(this.c); ++ packetdataserializer.writeShort(this.d); ++ packetdataserializer.writeByte(this.e); ++ packetdataserializer.writeByte(this.f); ++ packetdataserializer.writeBoolean(this.g); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutLightUpdate.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutLightUpdate.java +new file mode 100644 +index 0000000000000000000000000000000000000000..247d969e7d1aa59d9650fce1032aaa09db3903e5 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutLightUpdate.java +@@ -0,0 +1,159 @@ ++package net.minecraft.network.protocol.game; ++ ++import com.google.common.collect.Lists; ++import java.io.IOException; ++import java.util.Iterator; ++import java.util.List; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.chunk.NibbleArray; ++import net.minecraft.world.level.lighting.LightEngine; ++ ++public class PacketPlayOutLightUpdate implements Packet { ++ ++ private int a; ++ private int b; ++ private int c; ++ private int d; ++ private int e; ++ private int f; ++ private List g; ++ private List h; ++ private boolean i; ++ ++ public PacketPlayOutLightUpdate() {} ++ ++ public PacketPlayOutLightUpdate(ChunkCoordIntPair chunkcoordintpair, LightEngine lightengine, boolean flag) { ++ this.a = chunkcoordintpair.x; ++ this.b = chunkcoordintpair.z; ++ this.i = flag; ++ this.g = Lists.newArrayList(); ++ this.h = Lists.newArrayList(); ++ ++ for (int i = 0; i < 18; ++i) { ++ NibbleArray nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + i)); ++ NibbleArray nibblearray1 = lightengine.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, -1 + i)); ++ ++ if (nibblearray != null) { ++ if (nibblearray.c()) { ++ this.e |= 1 << i; ++ } else { ++ this.c |= 1 << i; ++ this.g.add(nibblearray.asBytes().clone()); ++ } ++ } ++ ++ if (nibblearray1 != null) { ++ if (nibblearray1.c()) { ++ this.f |= 1 << i; ++ } else { ++ this.d |= 1 << i; ++ this.h.add(nibblearray1.asBytes().clone()); ++ } ++ } ++ } ++ ++ } ++ ++ public PacketPlayOutLightUpdate(ChunkCoordIntPair chunkcoordintpair, LightEngine lightengine, int i, int j, boolean flag) { ++ this.a = chunkcoordintpair.x; ++ this.b = chunkcoordintpair.z; ++ this.i = flag; ++ this.c = i; ++ this.d = j; ++ this.g = Lists.newArrayList(); ++ this.h = Lists.newArrayList(); ++ ++ for (int k = 0; k < 18; ++k) { ++ NibbleArray nibblearray; ++ ++ if ((this.c & 1 << k) != 0) { ++ nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + k)); ++ if (nibblearray != null && !nibblearray.c()) { ++ this.g.add(nibblearray.asBytes().clone()); ++ } else { ++ this.c &= ~(1 << k); ++ if (nibblearray != null) { ++ this.e |= 1 << k; ++ } ++ } ++ } ++ ++ if ((this.d & 1 << k) != 0) { ++ nibblearray = lightengine.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, -1 + k)); ++ if (nibblearray != null && !nibblearray.c()) { ++ this.h.add(nibblearray.asBytes().clone()); ++ } else { ++ this.d &= ~(1 << k); ++ if (nibblearray != null) { ++ this.f |= 1 << k; ++ } ++ } ++ } ++ } ++ ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.i(); ++ this.b = packetdataserializer.i(); ++ this.i = packetdataserializer.readBoolean(); ++ this.c = packetdataserializer.i(); ++ this.d = packetdataserializer.i(); ++ this.e = packetdataserializer.i(); ++ this.f = packetdataserializer.i(); ++ this.g = Lists.newArrayList(); ++ ++ int i; ++ ++ for (i = 0; i < 18; ++i) { ++ if ((this.c & 1 << i) != 0) { ++ this.g.add(packetdataserializer.b(2048)); ++ } ++ } ++ ++ this.h = Lists.newArrayList(); ++ ++ for (i = 0; i < 18; ++i) { ++ if ((this.d & 1 << i) != 0) { ++ this.h.add(packetdataserializer.b(2048)); ++ } ++ } ++ ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.d(this.a); ++ packetdataserializer.d(this.b); ++ packetdataserializer.writeBoolean(this.i); ++ packetdataserializer.d(this.c); ++ packetdataserializer.d(this.d); ++ packetdataserializer.d(this.e); ++ packetdataserializer.d(this.f); ++ Iterator iterator = this.g.iterator(); ++ ++ byte[] abyte; ++ ++ while (iterator.hasNext()) { ++ abyte = (byte[]) iterator.next(); ++ packetdataserializer.a(abyte); ++ } ++ ++ iterator = this.h.iterator(); ++ ++ while (iterator.hasNext()) { ++ abyte = (byte[]) iterator.next(); ++ packetdataserializer.a(abyte); ++ } ++ ++ } ++ ++ public void a(PacketListenerPlayOut packetlistenerplayout) { ++ packetlistenerplayout.a(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +new file mode 100644 +index 0000000000000000000000000000000000000000..820ba7c59e7bc7b6f3311f1a4ec3d724e265a2af +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +@@ -0,0 +1,179 @@ ++package net.minecraft.network.protocol.game; ++ ++import com.google.common.collect.Lists; ++import io.netty.buffer.ByteBuf; ++import io.netty.buffer.Unpooled; ++import java.io.IOException; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map.Entry; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagLongArray; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.chunk.BiomeStorage; ++import net.minecraft.world.level.chunk.Chunk; ++import net.minecraft.world.level.chunk.ChunkSection; ++import net.minecraft.world.level.levelgen.HeightMap; ++ ++public class PacketPlayOutMapChunk implements Packet { ++ ++ private int a; ++ private int b; ++ private int c; ++ private NBTTagCompound d; ++ @Nullable ++ private int[] e; ++ private byte[] f; ++ private List g; ++ private boolean h; ++ ++ public PacketPlayOutMapChunk() {} ++ ++ public PacketPlayOutMapChunk(Chunk chunk, int i) { ++ ChunkCoordIntPair chunkcoordintpair = chunk.getPos(); ++ ++ this.a = chunkcoordintpair.x; ++ this.b = chunkcoordintpair.z; ++ this.h = i == 65535; ++ this.d = new NBTTagCompound(); ++ Iterator iterator = chunk.f().iterator(); ++ ++ Entry entry; ++ ++ while (iterator.hasNext()) { ++ entry = (Entry) iterator.next(); ++ if (((HeightMap.Type) entry.getKey()).c()) { ++ this.d.set(((HeightMap.Type) entry.getKey()).b(), new NBTTagLongArray(((HeightMap) entry.getValue()).a())); ++ } ++ } ++ ++ if (this.h) { ++ this.e = chunk.getBiomeIndex().a(); ++ } ++ ++ this.f = new byte[this.a(chunk, i)]; ++ this.c = this.a(new PacketDataSerializer(this.j()), chunk, i); ++ this.g = Lists.newArrayList(); ++ iterator = chunk.getTileEntities().entrySet().iterator(); ++ ++ while (iterator.hasNext()) { ++ entry = (Entry) iterator.next(); ++ BlockPosition blockposition = (BlockPosition) entry.getKey(); ++ TileEntity tileentity = (TileEntity) entry.getValue(); ++ int j = blockposition.getY() >> 4; ++ ++ if (this.f() || (i & 1 << j) != 0) { ++ NBTTagCompound nbttagcompound = tileentity.b(); ++ ++ this.g.add(nbttagcompound); ++ } ++ } ++ ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.readInt(); ++ this.b = packetdataserializer.readInt(); ++ this.h = packetdataserializer.readBoolean(); ++ this.c = packetdataserializer.i(); ++ this.d = packetdataserializer.l(); ++ if (this.h) { ++ this.e = packetdataserializer.c(BiomeStorage.a); ++ } ++ ++ int i = packetdataserializer.i(); ++ ++ if (i > 2097152) { ++ throw new RuntimeException("Chunk Packet trying to allocate too much memory on read."); ++ } else { ++ this.f = new byte[i]; ++ packetdataserializer.readBytes(this.f); ++ int j = packetdataserializer.i(); ++ ++ this.g = Lists.newArrayList(); ++ ++ for (int k = 0; k < j; ++k) { ++ this.g.add(packetdataserializer.l()); ++ } ++ ++ } ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.writeInt(this.a); ++ packetdataserializer.writeInt(this.b); ++ packetdataserializer.writeBoolean(this.h); ++ packetdataserializer.d(this.c); ++ packetdataserializer.a(this.d); ++ if (this.e != null) { ++ packetdataserializer.a(this.e); ++ } ++ ++ packetdataserializer.d(this.f.length); ++ packetdataserializer.writeBytes(this.f); ++ packetdataserializer.d(this.g.size()); ++ Iterator iterator = this.g.iterator(); ++ ++ while (iterator.hasNext()) { ++ NBTTagCompound nbttagcompound = (NBTTagCompound) iterator.next(); ++ ++ packetdataserializer.a(nbttagcompound); ++ } ++ ++ } ++ ++ public void a(PacketListenerPlayOut packetlistenerplayout) { ++ packetlistenerplayout.a(this); ++ } ++ ++ private ByteBuf j() { ++ ByteBuf bytebuf = Unpooled.wrappedBuffer(this.f); ++ ++ bytebuf.writerIndex(0); ++ return bytebuf; ++ } ++ ++ public int a(PacketDataSerializer packetdataserializer, Chunk chunk, int i) { ++ int j = 0; ++ ChunkSection[] achunksection = chunk.getSections(); ++ int k = 0; ++ ++ for (int l = achunksection.length; k < l; ++k) { ++ ChunkSection chunksection = achunksection[k]; ++ ++ if (chunksection != Chunk.a && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { ++ j |= 1 << k; ++ chunksection.b(packetdataserializer); ++ } ++ } ++ ++ return j; ++ } ++ ++ protected int a(Chunk chunk, int i) { ++ int j = 0; ++ ChunkSection[] achunksection = chunk.getSections(); ++ int k = 0; ++ ++ for (int l = achunksection.length; k < l; ++k) { ++ ChunkSection chunksection = achunksection[k]; ++ ++ if (chunksection != Chunk.a && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { ++ j += chunksection.j(); ++ } ++ } ++ ++ return j; ++ } ++ ++ public boolean f() { ++ return this.h; ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMount.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMount.java +new file mode 100644 +index 0000000000000000000000000000000000000000..edc6fff87c4abad2c123b1a46d6e5b792602b3be +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMount.java +@@ -0,0 +1,43 @@ ++package net.minecraft.network.protocol.game; ++ ++import java.io.IOException; ++import java.util.List; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.world.entity.Entity; ++ ++public class PacketPlayOutMount implements Packet { ++ ++ private int a; ++ private int[] b; ++ ++ public PacketPlayOutMount() {} ++ ++ public PacketPlayOutMount(Entity entity) { ++ this.a = entity.getId(); ++ List list = entity.getPassengers(); ++ ++ this.b = new int[list.size()]; ++ ++ for (int i = 0; i < list.size(); ++i) { ++ this.b[i] = ((Entity) list.get(i)).getId(); ++ } ++ ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.i(); ++ this.b = packetdataserializer.b(); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.d(this.a); ++ packetdataserializer.a(this.b); ++ } ++ ++ public void a(PacketListenerPlayOut packetlistenerplayout) { ++ packetlistenerplayout.a(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutPlayerListHeaderFooter.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutPlayerListHeaderFooter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0268b8e6595ee919bcd55a74ba872a2b7d2a17d8 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutPlayerListHeaderFooter.java +@@ -0,0 +1,30 @@ ++package net.minecraft.network.protocol.game; ++ ++import java.io.IOException; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.network.protocol.Packet; ++ ++public class PacketPlayOutPlayerListHeaderFooter implements Packet { ++ ++ public IChatBaseComponent header; ++ public IChatBaseComponent footer; ++ ++ public PacketPlayOutPlayerListHeaderFooter() {} ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.header = packetdataserializer.h(); ++ this.footer = packetdataserializer.h(); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.a(this.header); ++ packetdataserializer.a(this.footer); ++ } ++ ++ public void a(PacketListenerPlayOut packetlistenerplayout) { ++ packetlistenerplayout.a(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutScoreboardTeam.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutScoreboardTeam.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bc40f2cbe1645fd60c4cee106b90f17cd043d32d +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutScoreboardTeam.java +@@ -0,0 +1,137 @@ ++package net.minecraft.network.protocol.game; ++ ++import com.google.common.collect.Lists; ++import java.io.IOException; ++import java.util.Collection; ++import java.util.Iterator; ++import net.minecraft.EnumChatFormat; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.chat.ChatComponentText; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.world.scores.ScoreboardTeam; ++import net.minecraft.world.scores.ScoreboardTeamBase; ++ ++public class PacketPlayOutScoreboardTeam implements Packet { ++ ++ private String a = ""; ++ private IChatBaseComponent b; ++ private IChatBaseComponent c; ++ private IChatBaseComponent d; ++ private String e; ++ private String f; ++ private EnumChatFormat g; ++ private final Collection h; ++ private int i; ++ private int j; ++ ++ public PacketPlayOutScoreboardTeam() { ++ this.b = ChatComponentText.d; ++ this.c = ChatComponentText.d; ++ this.d = ChatComponentText.d; ++ this.e = ScoreboardTeamBase.EnumNameTagVisibility.ALWAYS.e; ++ this.f = ScoreboardTeamBase.EnumTeamPush.ALWAYS.e; ++ this.g = EnumChatFormat.RESET; ++ this.h = Lists.newArrayList(); ++ } ++ ++ public PacketPlayOutScoreboardTeam(ScoreboardTeam scoreboardteam, int i) { ++ this.b = ChatComponentText.d; ++ this.c = ChatComponentText.d; ++ this.d = ChatComponentText.d; ++ this.e = ScoreboardTeamBase.EnumNameTagVisibility.ALWAYS.e; ++ this.f = ScoreboardTeamBase.EnumTeamPush.ALWAYS.e; ++ this.g = EnumChatFormat.RESET; ++ this.h = Lists.newArrayList(); ++ this.a = scoreboardteam.getName(); ++ this.i = i; ++ if (i == 0 || i == 2) { ++ this.b = scoreboardteam.getDisplayName(); ++ this.j = scoreboardteam.packOptionData(); ++ this.e = scoreboardteam.getNameTagVisibility().e; ++ this.f = scoreboardteam.getCollisionRule().e; ++ this.g = scoreboardteam.getColor(); ++ this.c = scoreboardteam.getPrefix(); ++ this.d = scoreboardteam.getSuffix(); ++ } ++ ++ if (i == 0) { ++ this.h.addAll(scoreboardteam.getPlayerNameSet()); ++ } ++ ++ } ++ ++ public PacketPlayOutScoreboardTeam(ScoreboardTeam scoreboardteam, Collection collection, int i) { ++ this.b = ChatComponentText.d; ++ this.c = ChatComponentText.d; ++ this.d = ChatComponentText.d; ++ this.e = ScoreboardTeamBase.EnumNameTagVisibility.ALWAYS.e; ++ this.f = ScoreboardTeamBase.EnumTeamPush.ALWAYS.e; ++ this.g = EnumChatFormat.RESET; ++ this.h = Lists.newArrayList(); ++ if (i != 3 && i != 4) { ++ throw new IllegalArgumentException("Method must be join or leave for player constructor"); ++ } else if (collection != null && !collection.isEmpty()) { ++ this.i = i; ++ this.a = scoreboardteam.getName(); ++ this.h.addAll(collection); ++ } else { ++ throw new IllegalArgumentException("Players cannot be null/empty"); ++ } ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.e(16); ++ this.i = packetdataserializer.readByte(); ++ if (this.i == 0 || this.i == 2) { ++ this.b = packetdataserializer.h(); ++ this.j = packetdataserializer.readByte(); ++ this.e = packetdataserializer.e(40); ++ this.f = packetdataserializer.e(40); ++ this.g = (EnumChatFormat) packetdataserializer.a(EnumChatFormat.class); ++ this.c = packetdataserializer.h(); ++ this.d = packetdataserializer.h(); ++ } ++ ++ if (this.i == 0 || this.i == 3 || this.i == 4) { ++ int i = packetdataserializer.i(); ++ ++ for (int j = 0; j < i; ++j) { ++ this.h.add(packetdataserializer.e(40)); ++ } ++ } ++ ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.a(this.a); ++ packetdataserializer.writeByte(this.i); ++ if (this.i == 0 || this.i == 2) { ++ packetdataserializer.a(this.b); ++ packetdataserializer.writeByte(this.j); ++ packetdataserializer.a(this.e); ++ packetdataserializer.a(this.f); ++ packetdataserializer.a((Enum) this.g); ++ packetdataserializer.a(this.c); ++ packetdataserializer.a(this.d); ++ } ++ ++ if (this.i == 0 || this.i == 3 || this.i == 4) { ++ packetdataserializer.d(this.h.size()); ++ Iterator iterator = this.h.iterator(); ++ ++ while (iterator.hasNext()) { ++ String s = (String) iterator.next(); ++ ++ packetdataserializer.a(s); ++ } ++ } ++ ++ } ++ ++ public void a(PacketListenerPlayOut packetlistenerplayout) { ++ packetlistenerplayout.a(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutTitle.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutTitle.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9c44a3d9273afaf4d35f4ff86727386b34d9eb06 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutTitle.java +@@ -0,0 +1,75 @@ ++package net.minecraft.network.protocol.game; ++ ++import java.io.IOException; ++import javax.annotation.Nullable; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.network.protocol.Packet; ++ ++public class PacketPlayOutTitle implements Packet { ++ ++ private PacketPlayOutTitle.EnumTitleAction a; ++ private IChatBaseComponent b; ++ private int c; ++ private int d; ++ private int e; ++ ++ public PacketPlayOutTitle() {} ++ ++ public PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction packetplayouttitle_enumtitleaction, IChatBaseComponent ichatbasecomponent) { ++ this(packetplayouttitle_enumtitleaction, ichatbasecomponent, -1, -1, -1); ++ } ++ ++ public PacketPlayOutTitle(int i, int j, int k) { ++ this(PacketPlayOutTitle.EnumTitleAction.TIMES, (IChatBaseComponent) null, i, j, k); ++ } ++ ++ public PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction packetplayouttitle_enumtitleaction, @Nullable IChatBaseComponent ichatbasecomponent, int i, int j, int k) { ++ this.a = packetplayouttitle_enumtitleaction; ++ this.b = ichatbasecomponent; ++ this.c = i; ++ this.d = j; ++ this.e = k; ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = (PacketPlayOutTitle.EnumTitleAction) packetdataserializer.a(PacketPlayOutTitle.EnumTitleAction.class); ++ if (this.a == PacketPlayOutTitle.EnumTitleAction.TITLE || this.a == PacketPlayOutTitle.EnumTitleAction.SUBTITLE || this.a == PacketPlayOutTitle.EnumTitleAction.ACTIONBAR) { ++ this.b = packetdataserializer.h(); ++ } ++ ++ if (this.a == PacketPlayOutTitle.EnumTitleAction.TIMES) { ++ this.c = packetdataserializer.readInt(); ++ this.d = packetdataserializer.readInt(); ++ this.e = packetdataserializer.readInt(); ++ } ++ ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.a((Enum) this.a); ++ if (this.a == PacketPlayOutTitle.EnumTitleAction.TITLE || this.a == PacketPlayOutTitle.EnumTitleAction.SUBTITLE || this.a == PacketPlayOutTitle.EnumTitleAction.ACTIONBAR) { ++ packetdataserializer.a(this.b); ++ } ++ ++ if (this.a == PacketPlayOutTitle.EnumTitleAction.TIMES) { ++ packetdataserializer.writeInt(this.c); ++ packetdataserializer.writeInt(this.d); ++ packetdataserializer.writeInt(this.e); ++ } ++ ++ } ++ ++ public void a(PacketListenerPlayOut packetlistenerplayout) { ++ packetlistenerplayout.a(this); ++ } ++ ++ public static enum EnumTitleAction { ++ ++ TITLE, SUBTITLE, ACTIONBAR, TIMES, CLEAR, RESET; ++ ++ private EnumTitleAction() {} ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutUpdateTime.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutUpdateTime.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a69e60a8934493f6786ce3d425f6dccb6e4befdd +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutUpdateTime.java +@@ -0,0 +1,41 @@ ++package net.minecraft.network.protocol.game; ++ ++import java.io.IOException; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++ ++public class PacketPlayOutUpdateTime implements Packet { ++ ++ private long a; ++ private long b; ++ ++ public PacketPlayOutUpdateTime() {} ++ ++ public PacketPlayOutUpdateTime(long i, long j, boolean flag) { ++ this.a = i; ++ this.b = j; ++ if (!flag) { ++ this.b = -this.b; ++ if (this.b == 0L) { ++ this.b = -1L; ++ } ++ } ++ ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.readLong(); ++ this.b = packetdataserializer.readLong(); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.writeLong(this.a); ++ packetdataserializer.writeLong(this.b); ++ } ++ ++ public void a(PacketListenerPlayOut packetlistenerplayout) { ++ packetlistenerplayout.a(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutWindowItems.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutWindowItems.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b90e35a0099a2482f8fc2998bd079fc2fe6439e6 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutWindowItems.java +@@ -0,0 +1,58 @@ ++package net.minecraft.network.protocol.game; ++ ++import java.io.IOException; ++import java.util.Iterator; ++import java.util.List; ++import net.minecraft.core.NonNullList; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.world.item.ItemStack; ++ ++public class PacketPlayOutWindowItems implements Packet { ++ ++ private int a; ++ private List b; ++ ++ public PacketPlayOutWindowItems() {} ++ ++ public PacketPlayOutWindowItems(int i, NonNullList nonnulllist) { ++ this.a = i; ++ this.b = NonNullList.a(nonnulllist.size(), ItemStack.b); ++ ++ for (int j = 0; j < this.b.size(); ++j) { ++ this.b.set(j, ((ItemStack) nonnulllist.get(j)).cloneItemStack()); ++ } ++ ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.readUnsignedByte(); ++ short short0 = packetdataserializer.readShort(); ++ ++ this.b = NonNullList.a(short0, ItemStack.b); ++ ++ for (int i = 0; i < short0; ++i) { ++ this.b.set(i, packetdataserializer.n()); ++ } ++ ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.writeByte(this.a); ++ packetdataserializer.writeShort(this.b.size()); ++ Iterator iterator = this.b.iterator(); ++ ++ while (iterator.hasNext()) { ++ ItemStack itemstack = (ItemStack) iterator.next(); ++ ++ packetdataserializer.a(itemstack); ++ } ++ ++ } ++ ++ public void a(PacketListenerPlayOut packetlistenerplayout) { ++ packetlistenerplayout.a(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/login/PacketLoginInCustomPayload.java b/src/main/java/net/minecraft/network/protocol/login/PacketLoginInCustomPayload.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c1bac2d07e5107c1346f246f5d5d929c73912bfd +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/login/PacketLoginInCustomPayload.java +@@ -0,0 +1,46 @@ ++package net.minecraft.network.protocol.login; ++ ++import java.io.IOException; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++ ++public class PacketLoginInCustomPayload implements Packet { ++ ++ private int a; ++ private PacketDataSerializer b; ++ ++ public PacketLoginInCustomPayload() {} ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.i(); ++ if (packetdataserializer.readBoolean()) { ++ int i = packetdataserializer.readableBytes(); ++ ++ if (i < 0 || i > 1048576) { ++ throw new IOException("Payload may not be larger than 1048576 bytes"); ++ } ++ ++ this.b = new PacketDataSerializer(packetdataserializer.readBytes(i)); ++ } else { ++ this.b = null; ++ } ++ ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.d(this.a); ++ if (this.b != null) { ++ packetdataserializer.writeBoolean(true); ++ packetdataserializer.writeBytes(this.b.copy()); ++ } else { ++ packetdataserializer.writeBoolean(false); ++ } ++ ++ } ++ ++ public void a(PacketLoginInListener packetlogininlistener) { ++ packetlogininlistener.a(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/login/PacketLoginOutCustomPayload.java b/src/main/java/net/minecraft/network/protocol/login/PacketLoginOutCustomPayload.java +new file mode 100644 +index 0000000000000000000000000000000000000000..eb970c1e954cb0aa83aa12e83c471778809e69b2 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/login/PacketLoginOutCustomPayload.java +@@ -0,0 +1,39 @@ ++package net.minecraft.network.protocol.login; ++ ++import java.io.IOException; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.resources.MinecraftKey; ++ ++public class PacketLoginOutCustomPayload implements Packet { ++ ++ private int a; ++ private MinecraftKey b; ++ private PacketDataSerializer c; ++ ++ public PacketLoginOutCustomPayload() {} ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.a = packetdataserializer.i(); ++ this.b = packetdataserializer.p(); ++ int i = packetdataserializer.readableBytes(); ++ ++ if (i >= 0 && i <= 1048576) { ++ this.c = new PacketDataSerializer(packetdataserializer.readBytes(i)); ++ } else { ++ throw new IOException("Payload may not be larger than 1048576 bytes"); ++ } ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.d(this.a); ++ packetdataserializer.a(this.b); ++ packetdataserializer.writeBytes(this.c.copy()); ++ } ++ ++ public void a(PacketLoginOutListener packetloginoutlistener) { ++ packetloginoutlistener.a(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/status/PacketStatusOutServerInfo.java b/src/main/java/net/minecraft/network/protocol/status/PacketStatusOutServerInfo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0ebeacaaeb265d202f52c758566a5160c42e8a55 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/status/PacketStatusOutServerInfo.java +@@ -0,0 +1,37 @@ ++package net.minecraft.network.protocol.status; ++ ++import com.google.gson.Gson; ++import com.google.gson.GsonBuilder; ++import java.io.IOException; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.network.chat.ChatModifier; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.util.ChatDeserializer; ++import net.minecraft.util.ChatTypeAdapterFactory; ++ ++public class PacketStatusOutServerInfo implements Packet { ++ ++ private static final Gson a = (new GsonBuilder()).registerTypeAdapter(ServerPing.ServerData.class, new ServerPing.ServerData.Serializer()).registerTypeAdapter(ServerPing.ServerPingPlayerSample.class, new ServerPing.ServerPingPlayerSample.Serializer()).registerTypeAdapter(ServerPing.class, new ServerPing.Serializer()).registerTypeHierarchyAdapter(IChatBaseComponent.class, new IChatBaseComponent.ChatSerializer()).registerTypeHierarchyAdapter(ChatModifier.class, new ChatModifier.ChatModifierSerializer()).registerTypeAdapterFactory(new ChatTypeAdapterFactory()).create(); ++ private ServerPing b; ++ ++ public PacketStatusOutServerInfo() {} ++ ++ public PacketStatusOutServerInfo(ServerPing serverping) { ++ this.b = serverping; ++ } ++ ++ @Override ++ public void a(PacketDataSerializer packetdataserializer) throws IOException { ++ this.b = (ServerPing) ChatDeserializer.a(PacketStatusOutServerInfo.a, packetdataserializer.e(32767), ServerPing.class); ++ } ++ ++ @Override ++ public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ packetdataserializer.a(PacketStatusOutServerInfo.a.toJson(this.b)); ++ } ++ ++ public void a(PacketStatusOutListener packetstatusoutlistener) { ++ packetstatusoutlistener.a(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/status/ServerPing.java b/src/main/java/net/minecraft/network/protocol/status/ServerPing.java +new file mode 100644 +index 0000000000000000000000000000000000000000..005ae7a75dfb19152abb606da29acad07c85e499 +--- /dev/null ++++ b/src/main/java/net/minecraft/network/protocol/status/ServerPing.java +@@ -0,0 +1,225 @@ ++package net.minecraft.network.protocol.status; ++ ++import com.google.gson.JsonArray; ++import com.google.gson.JsonDeserializationContext; ++import com.google.gson.JsonDeserializer; ++import com.google.gson.JsonElement; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonParseException; ++import com.google.gson.JsonSerializationContext; ++import com.google.gson.JsonSerializer; ++import com.mojang.authlib.GameProfile; ++import java.lang.reflect.Type; ++import java.util.UUID; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.util.ChatDeserializer; ++ ++public class ServerPing { ++ ++ private IChatBaseComponent a; ++ private ServerPing.ServerPingPlayerSample b; ++ private ServerPing.ServerData c; ++ private String d; ++ ++ public ServerPing() {} ++ ++ public IChatBaseComponent a() { ++ return this.a; ++ } ++ ++ public void setMOTD(IChatBaseComponent ichatbasecomponent) { ++ this.a = ichatbasecomponent; ++ } ++ ++ public ServerPing.ServerPingPlayerSample b() { ++ return this.b; ++ } ++ ++ public void setPlayerSample(ServerPing.ServerPingPlayerSample serverping_serverpingplayersample) { ++ this.b = serverping_serverpingplayersample; ++ } ++ ++ public ServerPing.ServerData getServerData() { ++ return this.c; ++ } ++ ++ public void setServerInfo(ServerPing.ServerData serverping_serverdata) { ++ this.c = serverping_serverdata; ++ } ++ ++ public void setFavicon(String s) { ++ this.d = s; ++ } ++ ++ public String d() { ++ return this.d; ++ } ++ ++ public static class Serializer implements JsonDeserializer, JsonSerializer { ++ ++ public Serializer() {} ++ ++ public ServerPing deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { ++ JsonObject jsonobject = ChatDeserializer.m(jsonelement, "status"); ++ ServerPing serverping = new ServerPing(); ++ ++ if (jsonobject.has("description")) { ++ serverping.setMOTD((IChatBaseComponent) jsondeserializationcontext.deserialize(jsonobject.get("description"), IChatBaseComponent.class)); ++ } ++ ++ if (jsonobject.has("players")) { ++ serverping.setPlayerSample((ServerPing.ServerPingPlayerSample) jsondeserializationcontext.deserialize(jsonobject.get("players"), ServerPing.ServerPingPlayerSample.class)); ++ } ++ ++ if (jsonobject.has("version")) { ++ serverping.setServerInfo((ServerPing.ServerData) jsondeserializationcontext.deserialize(jsonobject.get("version"), ServerPing.ServerData.class)); ++ } ++ ++ if (jsonobject.has("favicon")) { ++ serverping.setFavicon(ChatDeserializer.h(jsonobject, "favicon")); ++ } ++ ++ return serverping; ++ } ++ ++ public JsonElement serialize(ServerPing serverping, Type type, JsonSerializationContext jsonserializationcontext) { ++ JsonObject jsonobject = new JsonObject(); ++ ++ if (serverping.a() != null) { ++ jsonobject.add("description", jsonserializationcontext.serialize(serverping.a())); ++ } ++ ++ if (serverping.b() != null) { ++ jsonobject.add("players", jsonserializationcontext.serialize(serverping.b())); ++ } ++ ++ if (serverping.getServerData() != null) { ++ jsonobject.add("version", jsonserializationcontext.serialize(serverping.getServerData())); ++ } ++ ++ if (serverping.d() != null) { ++ jsonobject.addProperty("favicon", serverping.d()); ++ } ++ ++ return jsonobject; ++ } ++ } ++ ++ public static class ServerData { ++ ++ private final String a; ++ private final int b; ++ ++ public ServerData(String s, int i) { ++ this.a = s; ++ this.b = i; ++ } ++ ++ public String a() { ++ return this.a; ++ } ++ ++ public int getProtocolVersion() { ++ return this.b; ++ } ++ ++ public static class Serializer implements JsonDeserializer, JsonSerializer { ++ ++ public Serializer() {} ++ ++ public ServerPing.ServerData deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { ++ JsonObject jsonobject = ChatDeserializer.m(jsonelement, "version"); ++ ++ return new ServerPing.ServerData(ChatDeserializer.h(jsonobject, "name"), ChatDeserializer.n(jsonobject, "protocol")); ++ } ++ ++ public JsonElement serialize(ServerPing.ServerData serverping_serverdata, Type type, JsonSerializationContext jsonserializationcontext) { ++ JsonObject jsonobject = new JsonObject(); ++ ++ jsonobject.addProperty("name", serverping_serverdata.a()); ++ jsonobject.addProperty("protocol", serverping_serverdata.getProtocolVersion()); ++ return jsonobject; ++ } ++ } ++ } ++ ++ public static class ServerPingPlayerSample { ++ ++ private final int a; ++ private final int b; ++ private GameProfile[] c; ++ ++ public ServerPingPlayerSample(int i, int j) { ++ this.a = i; ++ this.b = j; ++ } ++ ++ public int a() { ++ return this.a; ++ } ++ ++ public int b() { ++ return this.b; ++ } ++ ++ public GameProfile[] c() { ++ return this.c; ++ } ++ ++ public void a(GameProfile[] agameprofile) { ++ this.c = agameprofile; ++ } ++ ++ public static class Serializer implements JsonDeserializer, JsonSerializer { ++ ++ public Serializer() {} ++ ++ public ServerPing.ServerPingPlayerSample deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { ++ JsonObject jsonobject = ChatDeserializer.m(jsonelement, "players"); ++ ServerPing.ServerPingPlayerSample serverping_serverpingplayersample = new ServerPing.ServerPingPlayerSample(ChatDeserializer.n(jsonobject, "max"), ChatDeserializer.n(jsonobject, "online")); ++ ++ if (ChatDeserializer.d(jsonobject, "sample")) { ++ JsonArray jsonarray = ChatDeserializer.u(jsonobject, "sample"); ++ ++ if (jsonarray.size() > 0) { ++ GameProfile[] agameprofile = new GameProfile[jsonarray.size()]; ++ ++ for (int i = 0; i < agameprofile.length; ++i) { ++ JsonObject jsonobject1 = ChatDeserializer.m(jsonarray.get(i), "player[" + i + "]"); ++ String s = ChatDeserializer.h(jsonobject1, "id"); ++ ++ agameprofile[i] = new GameProfile(UUID.fromString(s), ChatDeserializer.h(jsonobject1, "name")); ++ } ++ ++ serverping_serverpingplayersample.a(agameprofile); ++ } ++ } ++ ++ return serverping_serverpingplayersample; ++ } ++ ++ public JsonElement serialize(ServerPing.ServerPingPlayerSample serverping_serverpingplayersample, Type type, JsonSerializationContext jsonserializationcontext) { ++ JsonObject jsonobject = new JsonObject(); ++ ++ jsonobject.addProperty("max", serverping_serverpingplayersample.a()); ++ jsonobject.addProperty("online", serverping_serverpingplayersample.b()); ++ if (serverping_serverpingplayersample.c() != null && serverping_serverpingplayersample.c().length > 0) { ++ JsonArray jsonarray = new JsonArray(); ++ ++ for (int i = 0; i < serverping_serverpingplayersample.c().length; ++i) { ++ JsonObject jsonobject1 = new JsonObject(); ++ UUID uuid = serverping_serverpingplayersample.c()[i].getId(); ++ ++ jsonobject1.addProperty("id", uuid == null ? "" : uuid.toString()); ++ jsonobject1.addProperty("name", serverping_serverpingplayersample.c()[i].getName()); ++ jsonarray.add(jsonobject1); ++ } ++ ++ jsonobject.add("sample", jsonarray); ++ } ++ ++ return jsonobject; ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/recipebook/AutoRecipe.java b/src/main/java/net/minecraft/recipebook/AutoRecipe.java +new file mode 100644 +index 0000000000000000000000000000000000000000..897f7270bae601b39d74d6a56a60f0ac7f1f6090 +--- /dev/null ++++ b/src/main/java/net/minecraft/recipebook/AutoRecipe.java +@@ -0,0 +1,245 @@ ++package net.minecraft.recipebook; ++ ++import com.google.common.collect.Lists; ++import it.unimi.dsi.fastutil.ints.IntArrayList; ++import it.unimi.dsi.fastutil.ints.IntList; ++import it.unimi.dsi.fastutil.ints.IntListIterator; ++import java.util.Iterator; ++import java.util.List; ++import javax.annotation.Nullable; ++import net.minecraft.network.protocol.game.PacketPlayOutAutoRecipe; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.world.IInventory; ++import net.minecraft.world.entity.player.AutoRecipeStackManager; ++import net.minecraft.world.entity.player.PlayerInventory; ++import net.minecraft.world.inventory.ContainerPlayer; ++import net.minecraft.world.inventory.ContainerRecipeBook; ++import net.minecraft.world.inventory.ContainerWorkbench; ++import net.minecraft.world.inventory.Slot; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.crafting.IRecipe; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class AutoRecipe implements AutoRecipeAbstract { ++ ++ protected static final Logger LOGGER = LogManager.getLogger(); ++ protected final AutoRecipeStackManager b = new AutoRecipeStackManager(); ++ protected PlayerInventory c; ++ protected ContainerRecipeBook d; ++ ++ public AutoRecipe(ContainerRecipeBook containerrecipebook) { ++ this.d = containerrecipebook; ++ } ++ ++ public void a(EntityPlayer entityplayer, @Nullable IRecipe irecipe, boolean flag) { ++ if (irecipe != null && entityplayer.getRecipeBook().b(irecipe)) { ++ this.c = entityplayer.inventory; ++ if (this.b() || entityplayer.isCreative()) { ++ this.b.a(); ++ entityplayer.inventory.a(this.b); ++ this.d.a(this.b); ++ if (this.b.a(irecipe, (IntList) null)) { ++ this.a(irecipe, flag); ++ } else { ++ this.a(); ++ entityplayer.playerConnection.sendPacket(new PacketPlayOutAutoRecipe(entityplayer.activeContainer.windowId, irecipe)); ++ } ++ ++ entityplayer.inventory.update(); ++ } ++ } ++ } ++ ++ protected void a() { ++ for (int i = 0; i < this.d.g() * this.d.h() + 1; ++i) { ++ if (i != this.d.f() || !(this.d instanceof ContainerWorkbench) && !(this.d instanceof ContainerPlayer)) { ++ this.a(i); ++ } ++ } ++ ++ this.d.e(); ++ } ++ ++ protected void a(int i) { ++ ItemStack itemstack = this.d.getSlot(i).getItem(); ++ ++ if (!itemstack.isEmpty()) { ++ for (; itemstack.getCount() > 0; this.d.getSlot(i).a(1)) { ++ int j = this.c.firstPartial(itemstack); ++ ++ if (j == -1) { ++ j = this.c.getFirstEmptySlotIndex(); ++ } ++ ++ ItemStack itemstack1 = itemstack.cloneItemStack(); ++ ++ itemstack1.setCount(1); ++ if (!this.c.c(j, itemstack1)) { ++ AutoRecipe.LOGGER.error("Can't find any space for item in the inventory"); ++ } ++ } ++ ++ } ++ } ++ ++ protected void a(IRecipe irecipe, boolean flag) { ++ boolean flag1 = this.d.a(irecipe); ++ int i = this.b.b(irecipe, (IntList) null); ++ int j; ++ ++ if (flag1) { ++ for (j = 0; j < this.d.h() * this.d.g() + 1; ++j) { ++ if (j != this.d.f()) { ++ ItemStack itemstack = this.d.getSlot(j).getItem(); ++ ++ if (!itemstack.isEmpty() && Math.min(i, itemstack.getMaxStackSize()) < itemstack.getCount() + 1) { ++ return; ++ } ++ } ++ } ++ } ++ ++ j = this.a(flag, i, flag1); ++ IntArrayList intarraylist = new IntArrayList(); ++ ++ if (this.b.a(irecipe, intarraylist, j)) { ++ int k = j; ++ IntListIterator intlistiterator = intarraylist.iterator(); ++ ++ while (intlistiterator.hasNext()) { ++ int l = (Integer) intlistiterator.next(); ++ int i1 = AutoRecipeStackManager.a(l).getMaxStackSize(); ++ ++ if (i1 < k) { ++ k = i1; ++ } ++ } ++ ++ if (this.b.a(irecipe, intarraylist, k)) { ++ this.a(); ++ this.a(this.d.g(), this.d.h(), this.d.f(), irecipe, intarraylist.iterator(), k); ++ } ++ } ++ ++ } ++ ++ @Override ++ public void a(Iterator iterator, int i, int j, int k, int l) { ++ Slot slot = this.d.getSlot(i); ++ ItemStack itemstack = AutoRecipeStackManager.a((Integer) iterator.next()); ++ ++ if (!itemstack.isEmpty()) { ++ for (int i1 = 0; i1 < j; ++i1) { ++ this.a(slot, itemstack); ++ } ++ } ++ ++ } ++ ++ protected int a(boolean flag, int i, boolean flag1) { ++ int j = 1; ++ ++ if (flag) { ++ j = i; ++ } else if (flag1) { ++ j = 64; ++ ++ for (int k = 0; k < this.d.g() * this.d.h() + 1; ++k) { ++ if (k != this.d.f()) { ++ ItemStack itemstack = this.d.getSlot(k).getItem(); ++ ++ if (!itemstack.isEmpty() && j > itemstack.getCount()) { ++ j = itemstack.getCount(); ++ } ++ } ++ } ++ ++ if (j < 64) { ++ ++j; ++ } ++ } ++ ++ return j; ++ } ++ ++ protected void a(Slot slot, ItemStack itemstack) { ++ int i = this.c.c(itemstack); ++ ++ if (i != -1) { ++ ItemStack itemstack1 = this.c.getItem(i).cloneItemStack(); ++ ++ if (!itemstack1.isEmpty()) { ++ if (itemstack1.getCount() > 1) { ++ this.c.splitStack(i, 1); ++ } else { ++ this.c.splitWithoutUpdate(i); ++ } ++ ++ itemstack1.setCount(1); ++ if (slot.getItem().isEmpty()) { ++ slot.set(itemstack1); ++ } else { ++ slot.getItem().add(1); ++ } ++ ++ } ++ } ++ } ++ ++ private boolean b() { ++ List list = Lists.newArrayList(); ++ int i = this.c(); ++ ++ for (int j = 0; j < this.d.g() * this.d.h() + 1; ++j) { ++ if (j != this.d.f()) { ++ ItemStack itemstack = this.d.getSlot(j).getItem().cloneItemStack(); ++ ++ if (!itemstack.isEmpty()) { ++ int k = this.c.firstPartial(itemstack); ++ ++ if (k == -1 && list.size() <= i) { ++ Iterator iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ ItemStack itemstack1 = (ItemStack) iterator.next(); ++ ++ if (itemstack1.doMaterialsMatch(itemstack) && itemstack1.getCount() != itemstack1.getMaxStackSize() && itemstack1.getCount() + itemstack.getCount() <= itemstack1.getMaxStackSize()) { ++ itemstack1.add(itemstack.getCount()); ++ itemstack.setCount(0); ++ break; ++ } ++ } ++ ++ if (!itemstack.isEmpty()) { ++ if (list.size() >= i) { ++ return false; ++ } ++ ++ list.add(itemstack); ++ } ++ } else if (k == -1) { ++ return false; ++ } ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ private int c() { ++ int i = 0; ++ Iterator iterator = this.c.items.iterator(); ++ ++ while (iterator.hasNext()) { ++ ItemStack itemstack = (ItemStack) iterator.next(); ++ ++ if (itemstack.isEmpty()) { ++ ++i; ++ } ++ } ++ ++ return i; ++ } ++} +diff --git a/src/main/java/net/minecraft/resources/ResourceKey.java b/src/main/java/net/minecraft/resources/ResourceKey.java +new file mode 100644 +index 0000000000000000000000000000000000000000..760579921927b4c8b0f20b2611b95fd626e4b27f +--- /dev/null ++++ b/src/main/java/net/minecraft/resources/ResourceKey.java +@@ -0,0 +1,53 @@ ++package net.minecraft.resources; ++ ++import com.google.common.collect.Maps; ++import java.util.Collections; ++import java.util.Map; ++import java.util.function.Function; ++import net.minecraft.core.IRegistry; ++ ++public class ResourceKey { ++ ++ private static final Map> a = Collections.synchronizedMap(Maps.newIdentityHashMap()); ++ private final MinecraftKey b; ++ private final MinecraftKey c; ++ ++ public static ResourceKey a(ResourceKey> resourcekey, MinecraftKey minecraftkey) { ++ return a(resourcekey.c, minecraftkey); ++ } ++ ++ public static ResourceKey> a(MinecraftKey minecraftkey) { ++ return a(IRegistry.d, minecraftkey); ++ } ++ ++ private static ResourceKey a(MinecraftKey minecraftkey, MinecraftKey minecraftkey1) { ++ String s = (minecraftkey + ":" + minecraftkey1).intern(); ++ ++ return (ResourceKey) ResourceKey.a.computeIfAbsent(s, (s1) -> { ++ return new ResourceKey<>(minecraftkey, minecraftkey1); ++ }); ++ } ++ ++ private ResourceKey(MinecraftKey minecraftkey, MinecraftKey minecraftkey1) { ++ this.b = minecraftkey; ++ this.c = minecraftkey1; ++ } ++ ++ public String toString() { ++ return "ResourceKey[" + this.b + " / " + this.c + ']'; ++ } ++ ++ public boolean a(ResourceKey> resourcekey) { ++ return this.b.equals(resourcekey.a()); ++ } ++ ++ public MinecraftKey a() { ++ return this.c; ++ } ++ ++ public static Function> b(ResourceKey> resourcekey) { ++ return (minecraftkey) -> { ++ return a(resourcekey, minecraftkey); ++ }; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/EULA.java b/src/main/java/net/minecraft/server/EULA.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a5171d28b960b12c2743ea68a36d747bc967697d +--- /dev/null ++++ b/src/main/java/net/minecraft/server/EULA.java +@@ -0,0 +1,99 @@ ++package net.minecraft.server; ++ ++import java.io.InputStream; ++import java.io.OutputStream; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.util.Properties; ++import net.minecraft.SharedConstants; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class EULA { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final Path b; ++ private final boolean c; ++ ++ public EULA(Path path) { ++ this.b = path; ++ this.c = SharedConstants.d || this.b(); ++ } ++ ++ private boolean b() { ++ try { ++ InputStream inputstream = Files.newInputStream(this.b); ++ Throwable throwable = null; ++ ++ boolean flag; ++ ++ try { ++ Properties properties = new Properties(); ++ ++ properties.load(inputstream); ++ flag = Boolean.parseBoolean(properties.getProperty("eula", "false")); ++ } catch (Throwable throwable1) { ++ throwable = throwable1; ++ throw throwable1; ++ } finally { ++ if (inputstream != null) { ++ if (throwable != null) { ++ try { ++ inputstream.close(); ++ } catch (Throwable throwable2) { ++ throwable.addSuppressed(throwable2); ++ } ++ } else { ++ inputstream.close(); ++ } ++ } ++ ++ } ++ ++ return flag; ++ } catch (Exception exception) { ++ EULA.LOGGER.warn("Failed to load {}", this.b); ++ this.c(); ++ return false; ++ } ++ } ++ ++ public boolean a() { ++ return this.c; ++ } ++ ++ private void c() { ++ if (!SharedConstants.d) { ++ try { ++ OutputStream outputstream = Files.newOutputStream(this.b); ++ Throwable throwable = null; ++ ++ try { ++ Properties properties = new Properties(); ++ ++ properties.setProperty("eula", "false"); ++ properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula)."); ++ } catch (Throwable throwable1) { ++ throwable = throwable1; ++ throw throwable1; ++ } finally { ++ if (outputstream != null) { ++ if (throwable != null) { ++ try { ++ outputstream.close(); ++ } catch (Throwable throwable2) { ++ throwable.addSuppressed(throwable2); ++ } ++ } else { ++ outputstream.close(); ++ } ++ } ++ ++ } ++ } catch (Exception exception) { ++ EULA.LOGGER.warn("Failed to save {}", this.b, exception); ++ } ++ ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/commands/CommandDifficulty.java b/src/main/java/net/minecraft/server/commands/CommandDifficulty.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1773fa44f55c6f6dcda0afceff4db39881861879 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/commands/CommandDifficulty.java +@@ -0,0 +1,52 @@ ++package net.minecraft.server.commands; ++ ++import com.mojang.brigadier.CommandDispatcher; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; ++import net.minecraft.commands.CommandListenerWrapper; ++import net.minecraft.network.chat.ChatMessage; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.world.EnumDifficulty; ++ ++public class CommandDifficulty { ++ ++ private static final DynamicCommandExceptionType a = new DynamicCommandExceptionType((object) -> { ++ return new ChatMessage("commands.difficulty.failure", new Object[]{object}); ++ }); ++ ++ public static void a(CommandDispatcher commanddispatcher) { ++ LiteralArgumentBuilder literalargumentbuilder = net.minecraft.commands.CommandDispatcher.a("difficulty"); ++ EnumDifficulty[] aenumdifficulty = EnumDifficulty.values(); ++ int i = aenumdifficulty.length; ++ ++ for (int j = 0; j < i; ++j) { ++ EnumDifficulty enumdifficulty = aenumdifficulty[j]; ++ ++ literalargumentbuilder.then(net.minecraft.commands.CommandDispatcher.a(enumdifficulty.c()).executes((commandcontext) -> { ++ return a((CommandListenerWrapper) commandcontext.getSource(), enumdifficulty); ++ })); ++ } ++ ++ commanddispatcher.register((LiteralArgumentBuilder) ((LiteralArgumentBuilder) literalargumentbuilder.requires((commandlistenerwrapper) -> { ++ return commandlistenerwrapper.hasPermission(2); ++ })).executes((commandcontext) -> { ++ EnumDifficulty enumdifficulty1 = ((CommandListenerWrapper) commandcontext.getSource()).getWorld().getDifficulty(); ++ ++ ((CommandListenerWrapper) commandcontext.getSource()).sendMessage(new ChatMessage("commands.difficulty.query", new Object[]{enumdifficulty1.b()}), false); ++ return enumdifficulty1.a(); ++ })); ++ } ++ ++ public static int a(CommandListenerWrapper commandlistenerwrapper, EnumDifficulty enumdifficulty) throws CommandSyntaxException { ++ MinecraftServer minecraftserver = commandlistenerwrapper.getServer(); ++ ++ if (minecraftserver.getSaveData().getDifficulty() == enumdifficulty) { ++ throw CommandDifficulty.a.create(enumdifficulty.c()); ++ } else { ++ minecraftserver.a(enumdifficulty, true); ++ commandlistenerwrapper.sendMessage(new ChatMessage("commands.difficulty.success", new Object[]{enumdifficulty.b()}), true); ++ return 0; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/commands/CommandGive.java b/src/main/java/net/minecraft/server/commands/CommandGive.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6685bf1757458d908e32d4069f7a8a22a28c28d7 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/commands/CommandGive.java +@@ -0,0 +1,77 @@ ++package net.minecraft.server.commands; ++ ++import com.mojang.brigadier.CommandDispatcher; ++import com.mojang.brigadier.arguments.ArgumentType; ++import com.mojang.brigadier.arguments.IntegerArgumentType; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import com.mojang.brigadier.builder.RequiredArgumentBuilder; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import java.util.Collection; ++import java.util.Iterator; ++import net.minecraft.commands.CommandListenerWrapper; ++import net.minecraft.commands.arguments.ArgumentEntity; ++import net.minecraft.commands.arguments.item.ArgumentItemStack; ++import net.minecraft.commands.arguments.item.ArgumentPredicateItemStack; ++import net.minecraft.network.chat.ChatMessage; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.world.entity.item.EntityItem; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.item.ItemStack; ++ ++public class CommandGive { ++ ++ public static void a(CommandDispatcher commanddispatcher) { ++ commanddispatcher.register((LiteralArgumentBuilder) ((LiteralArgumentBuilder) net.minecraft.commands.CommandDispatcher.a("give").requires((commandlistenerwrapper) -> { ++ return commandlistenerwrapper.hasPermission(2); ++ })).then(net.minecraft.commands.CommandDispatcher.a("targets", (ArgumentType) ArgumentEntity.d()).then(((RequiredArgumentBuilder) net.minecraft.commands.CommandDispatcher.a("item", (ArgumentType) ArgumentItemStack.a()).executes((commandcontext) -> { ++ return a((CommandListenerWrapper) commandcontext.getSource(), ArgumentItemStack.a(commandcontext, "item"), ArgumentEntity.f(commandcontext, "targets"), 1); ++ })).then(net.minecraft.commands.CommandDispatcher.a("count", (ArgumentType) IntegerArgumentType.integer(1)).executes((commandcontext) -> { ++ return a((CommandListenerWrapper) commandcontext.getSource(), ArgumentItemStack.a(commandcontext, "item"), ArgumentEntity.f(commandcontext, "targets"), IntegerArgumentType.getInteger(commandcontext, "count")); ++ }))))); ++ } ++ ++ private static int a(CommandListenerWrapper commandlistenerwrapper, ArgumentPredicateItemStack argumentpredicateitemstack, Collection collection, int i) throws CommandSyntaxException { ++ Iterator iterator = collection.iterator(); ++ ++ while (iterator.hasNext()) { ++ EntityPlayer entityplayer = (EntityPlayer) iterator.next(); ++ int j = i; ++ ++ while (j > 0) { ++ int k = Math.min(argumentpredicateitemstack.a().getMaxStackSize(), j); ++ ++ j -= k; ++ ItemStack itemstack = argumentpredicateitemstack.a(k, false); ++ boolean flag = entityplayer.inventory.pickup(itemstack); ++ EntityItem entityitem; ++ ++ if (flag && itemstack.isEmpty()) { ++ itemstack.setCount(1); ++ entityitem = entityplayer.drop(itemstack, false); ++ if (entityitem != null) { ++ entityitem.s(); ++ } ++ ++ entityplayer.world.playSound((EntityHuman) null, entityplayer.locX(), entityplayer.locY(), entityplayer.locZ(), SoundEffects.ENTITY_ITEM_PICKUP, SoundCategory.PLAYERS, 0.2F, ((entityplayer.getRandom().nextFloat() - entityplayer.getRandom().nextFloat()) * 0.7F + 1.0F) * 2.0F); ++ entityplayer.defaultContainer.c(); ++ } else { ++ entityitem = entityplayer.drop(itemstack, false); ++ if (entityitem != null) { ++ entityitem.n(); ++ entityitem.setOwner(entityplayer.getUniqueID()); ++ } ++ } ++ } ++ } ++ ++ if (collection.size() == 1) { ++ commandlistenerwrapper.sendMessage(new ChatMessage("commands.give.success.single", new Object[]{i, argumentpredicateitemstack.a(i, false).C(), ((EntityPlayer) collection.iterator().next()).getScoreboardDisplayName()}), true); ++ } else { ++ commandlistenerwrapper.sendMessage(new ChatMessage("commands.give.success.single", new Object[]{i, argumentpredicateitemstack.a(i, false).C(), collection.size()}), true); ++ } ++ ++ return collection.size(); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/commands/CommandSchedule.java b/src/main/java/net/minecraft/server/commands/CommandSchedule.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b88a91072032b75f83d811d63e1b5e3808faa9be +--- /dev/null ++++ b/src/main/java/net/minecraft/server/commands/CommandSchedule.java +@@ -0,0 +1,93 @@ ++package net.minecraft.server.commands; ++ ++import com.mojang.brigadier.CommandDispatcher; ++import com.mojang.brigadier.arguments.ArgumentType; ++import com.mojang.brigadier.arguments.IntegerArgumentType; ++import com.mojang.brigadier.arguments.StringArgumentType; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import com.mojang.brigadier.builder.RequiredArgumentBuilder; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; ++import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; ++import com.mojang.brigadier.suggestion.SuggestionProvider; ++import com.mojang.datafixers.util.Either; ++import com.mojang.datafixers.util.Pair; ++import net.minecraft.commands.CommandListenerWrapper; ++import net.minecraft.commands.CustomFunction; ++import net.minecraft.commands.ICompletionProvider; ++import net.minecraft.commands.arguments.ArgumentTime; ++import net.minecraft.commands.arguments.item.ArgumentTag; ++import net.minecraft.network.chat.ChatMessage; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.tags.Tag; ++import net.minecraft.world.level.timers.CustomFunctionCallback; ++import net.minecraft.world.level.timers.CustomFunctionCallbackTag; ++import net.minecraft.world.level.timers.CustomFunctionCallbackTimerQueue; ++ ++public class CommandSchedule { ++ ++ private static final SimpleCommandExceptionType a = new SimpleCommandExceptionType(new ChatMessage("commands.schedule.same_tick")); ++ private static final DynamicCommandExceptionType b = new DynamicCommandExceptionType((object) -> { ++ return new ChatMessage("commands.schedule.cleared.failure", new Object[]{object}); ++ }); ++ private static final SuggestionProvider c = (commandcontext, suggestionsbuilder) -> { ++ return ICompletionProvider.b((Iterable) ((CommandListenerWrapper) commandcontext.getSource()).getServer().getSaveData().H().u().a(), suggestionsbuilder); ++ }; ++ ++ public static void a(CommandDispatcher commanddispatcher) { ++ commanddispatcher.register((LiteralArgumentBuilder) ((LiteralArgumentBuilder) ((LiteralArgumentBuilder) net.minecraft.commands.CommandDispatcher.a("schedule").requires((commandlistenerwrapper) -> { ++ return commandlistenerwrapper.hasPermission(2); ++ })).then(net.minecraft.commands.CommandDispatcher.a("function").then(net.minecraft.commands.CommandDispatcher.a("function", (ArgumentType) ArgumentTag.a()).suggests(CommandFunction.a).then(((RequiredArgumentBuilder) ((RequiredArgumentBuilder) net.minecraft.commands.CommandDispatcher.a("time", (ArgumentType) ArgumentTime.a()).executes((commandcontext) -> { ++ return a((CommandListenerWrapper) commandcontext.getSource(), ArgumentTag.b(commandcontext, "function"), IntegerArgumentType.getInteger(commandcontext, "time"), true); ++ })).then(net.minecraft.commands.CommandDispatcher.a("append").executes((commandcontext) -> { ++ return a((CommandListenerWrapper) commandcontext.getSource(), ArgumentTag.b(commandcontext, "function"), IntegerArgumentType.getInteger(commandcontext, "time"), false); ++ }))).then(net.minecraft.commands.CommandDispatcher.a("replace").executes((commandcontext) -> { ++ return a((CommandListenerWrapper) commandcontext.getSource(), ArgumentTag.b(commandcontext, "function"), IntegerArgumentType.getInteger(commandcontext, "time"), true); ++ })))))).then(net.minecraft.commands.CommandDispatcher.a("clear").then(net.minecraft.commands.CommandDispatcher.a("function", (ArgumentType) StringArgumentType.greedyString()).suggests(CommandSchedule.c).executes((commandcontext) -> { ++ return a((CommandListenerWrapper) commandcontext.getSource(), StringArgumentType.getString(commandcontext, "function")); ++ })))); ++ } ++ ++ private static int a(CommandListenerWrapper commandlistenerwrapper, Pair>> pair, int i, boolean flag) throws CommandSyntaxException { ++ if (i == 0) { ++ throw CommandSchedule.a.create(); ++ } else { ++ long j = commandlistenerwrapper.getWorld().getTime() + (long) i; ++ MinecraftKey minecraftkey = (MinecraftKey) pair.getFirst(); ++ CustomFunctionCallbackTimerQueue customfunctioncallbacktimerqueue = commandlistenerwrapper.getServer().getSaveData().H().u(); ++ ++ ((Either) pair.getSecond()).ifLeft((customfunction) -> { ++ String s = minecraftkey.toString(); ++ ++ if (flag) { ++ customfunctioncallbacktimerqueue.a(s); ++ } ++ ++ customfunctioncallbacktimerqueue.a(s, j, new CustomFunctionCallback(minecraftkey)); ++ commandlistenerwrapper.sendMessage(new ChatMessage("commands.schedule.created.function", new Object[]{minecraftkey, i, j}), true); ++ }).ifRight((tag) -> { ++ String s = "#" + minecraftkey.toString(); ++ ++ if (flag) { ++ customfunctioncallbacktimerqueue.a(s); ++ } ++ ++ customfunctioncallbacktimerqueue.a(s, j, new CustomFunctionCallbackTag(minecraftkey)); ++ commandlistenerwrapper.sendMessage(new ChatMessage("commands.schedule.created.tag", new Object[]{minecraftkey, i, j}), true); ++ }); ++ return (int) Math.floorMod(j, 2147483647L); ++ } ++ } ++ ++ private static int a(CommandListenerWrapper commandlistenerwrapper, String s) throws CommandSyntaxException { ++ int i = commandlistenerwrapper.getServer().getSaveData().H().u().a(s); ++ ++ if (i == 0) { ++ throw CommandSchedule.b.create(s); ++ } else { ++ commandlistenerwrapper.sendMessage(new ChatMessage("commands.schedule.cleared.success", new Object[]{i, s}), true); ++ return i; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b13e6f9923a9c5703f4eaeab2d0c112e4726b496 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java +@@ -0,0 +1,143 @@ ++package net.minecraft.server.dedicated; ++ ++import com.mojang.authlib.GameProfile; ++import java.io.IOException; ++import net.minecraft.core.IRegistryCustom; ++import net.minecraft.server.players.PlayerList; ++import net.minecraft.world.level.storage.WorldNBTStorage; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class DedicatedPlayerList extends PlayerList { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ ++ public DedicatedPlayerList(DedicatedServer dedicatedserver, IRegistryCustom.Dimension iregistrycustom_dimension, WorldNBTStorage worldnbtstorage) { ++ super(dedicatedserver, iregistrycustom_dimension, worldnbtstorage, dedicatedserver.getDedicatedServerProperties().maxPlayers); ++ DedicatedServerProperties dedicatedserverproperties = dedicatedserver.getDedicatedServerProperties(); ++ ++ this.a(dedicatedserverproperties.viewDistance); ++ super.setHasWhitelist((Boolean) dedicatedserverproperties.whiteList.get()); ++ this.y(); ++ this.w(); ++ this.x(); ++ this.v(); ++ this.z(); ++ this.B(); ++ this.A(); ++ if (!this.getWhitelist().b().exists()) { ++ this.C(); ++ } ++ ++ } ++ ++ @Override ++ public void setHasWhitelist(boolean flag) { ++ super.setHasWhitelist(flag); ++ this.getServer().setHasWhitelist(flag); ++ } ++ ++ @Override ++ public void addOp(GameProfile gameprofile) { ++ super.addOp(gameprofile); ++ this.A(); ++ } ++ ++ @Override ++ public void removeOp(GameProfile gameprofile) { ++ super.removeOp(gameprofile); ++ this.A(); ++ } ++ ++ @Override ++ public void reloadWhitelist() { ++ this.B(); ++ } ++ ++ private void v() { ++ try { ++ this.getIPBans().save(); ++ } catch (IOException ioexception) { ++ DedicatedPlayerList.LOGGER.warn("Failed to save ip banlist: ", ioexception); ++ } ++ ++ } ++ ++ private void w() { ++ try { ++ this.getProfileBans().save(); ++ } catch (IOException ioexception) { ++ DedicatedPlayerList.LOGGER.warn("Failed to save user banlist: ", ioexception); ++ } ++ ++ } ++ ++ private void x() { ++ try { ++ this.getIPBans().load(); ++ } catch (IOException ioexception) { ++ DedicatedPlayerList.LOGGER.warn("Failed to load ip banlist: ", ioexception); ++ } ++ ++ } ++ ++ private void y() { ++ try { ++ this.getProfileBans().load(); ++ } catch (IOException ioexception) { ++ DedicatedPlayerList.LOGGER.warn("Failed to load user banlist: ", ioexception); ++ } ++ ++ } ++ ++ private void z() { ++ try { ++ this.getOPs().load(); ++ } catch (Exception exception) { ++ DedicatedPlayerList.LOGGER.warn("Failed to load operators list: ", exception); ++ } ++ ++ } ++ ++ private void A() { ++ try { ++ this.getOPs().save(); ++ } catch (Exception exception) { ++ DedicatedPlayerList.LOGGER.warn("Failed to save operators list: ", exception); ++ } ++ ++ } ++ ++ private void B() { ++ try { ++ this.getWhitelist().load(); ++ } catch (Exception exception) { ++ DedicatedPlayerList.LOGGER.warn("Failed to load white-list: ", exception); ++ } ++ ++ } ++ ++ private void C() { ++ try { ++ this.getWhitelist().save(); ++ } catch (Exception exception) { ++ DedicatedPlayerList.LOGGER.warn("Failed to save white-list: ", exception); ++ } ++ ++ } ++ ++ @Override ++ public boolean isWhitelisted(GameProfile gameprofile) { ++ return !this.getHasWhitelist() || this.isOp(gameprofile) || this.getWhitelist().isWhitelisted(gameprofile); ++ } ++ ++ @Override ++ public DedicatedServer getServer() { ++ return (DedicatedServer) super.getServer(); ++ } ++ ++ @Override ++ public boolean f(GameProfile gameprofile) { ++ return this.getOPs().b(gameprofile); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/gui/GuiStatsComponent.java b/src/main/java/net/minecraft/server/gui/GuiStatsComponent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7ac8a9af459656483dc693a3028ebf8c34628cc7 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/gui/GuiStatsComponent.java +@@ -0,0 +1,88 @@ ++package net.minecraft.server.gui; ++ ++import java.awt.Color; ++import java.awt.Dimension; ++import java.awt.Graphics; ++import java.text.DecimalFormat; ++import java.text.DecimalFormatSymbols; ++import java.util.Locale; ++import javax.swing.JComponent; ++import javax.swing.Timer; ++import net.minecraft.SystemUtils; ++import net.minecraft.server.MinecraftServer; ++ ++public class GuiStatsComponent extends JComponent { ++ ++ private static final DecimalFormat a = (DecimalFormat) SystemUtils.a((Object) (new DecimalFormat("########0.000")), (decimalformat) -> { ++ decimalformat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)); ++ }); ++ private final int[] b = new int[256]; ++ private int c; ++ private final String[] d = new String[11]; ++ private final MinecraftServer e; ++ private final Timer f; ++ ++ public GuiStatsComponent(MinecraftServer minecraftserver) { ++ this.e = minecraftserver; ++ this.setPreferredSize(new Dimension(456, 246)); ++ this.setMinimumSize(new Dimension(456, 246)); ++ this.setMaximumSize(new Dimension(456, 246)); ++ this.f = new Timer(500, (actionevent) -> { ++ this.b(); ++ }); ++ this.f.start(); ++ this.setBackground(Color.BLACK); ++ } ++ ++ private void b() { ++ long i = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); ++ ++ this.d[0] = "Memory use: " + i / 1024L / 1024L + " mb (" + Runtime.getRuntime().freeMemory() * 100L / Runtime.getRuntime().maxMemory() + "% free)"; ++ this.d[1] = "Avg tick: " + GuiStatsComponent.a.format(this.a(this.e.h) * 1.0E-6D) + " ms"; ++ this.b[this.c++ & 255] = (int) (i * 100L / Runtime.getRuntime().maxMemory()); ++ this.repaint(); ++ } ++ ++ private double a(long[] along) { ++ long i = 0L; ++ long[] along1 = along; ++ int j = along.length; ++ ++ for (int k = 0; k < j; ++k) { ++ long l = along1[k]; ++ ++ i += l; ++ } ++ ++ return (double) i / (double) along.length; ++ } ++ ++ public void paint(Graphics graphics) { ++ graphics.setColor(new Color(16777215)); ++ graphics.fillRect(0, 0, 456, 246); ++ ++ int i; ++ ++ for (i = 0; i < 256; ++i) { ++ int j = this.b[i + this.c & 255]; ++ ++ graphics.setColor(new Color(j + 28 << 16)); ++ graphics.fillRect(i, 100 - j, 1, j); ++ } ++ ++ graphics.setColor(Color.BLACK); ++ ++ for (i = 0; i < this.d.length; ++i) { ++ String s = this.d[i]; ++ ++ if (s != null) { ++ graphics.drawString(s, 32, 116 + i * 16); ++ } ++ } ++ ++ } ++ ++ public void a() { ++ this.f.stop(); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/LightEngineGraphSection.java b/src/main/java/net/minecraft/server/level/LightEngineGraphSection.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c8bb040e7ed848877ec9c2f9b30dcda137cadf35 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/level/LightEngineGraphSection.java +@@ -0,0 +1,74 @@ ++package net.minecraft.server.level; ++ ++import net.minecraft.core.SectionPosition; ++import net.minecraft.world.level.lighting.LightEngineGraph; ++ ++public abstract class LightEngineGraphSection extends LightEngineGraph { ++ ++ protected LightEngineGraphSection(int i, int j, int k) { ++ super(i, j, k); ++ } ++ ++ @Override ++ protected boolean a(long i) { ++ return i == Long.MAX_VALUE; ++ } ++ ++ @Override ++ protected void a(long i, int j, boolean flag) { ++ for (int k = -1; k <= 1; ++k) { ++ for (int l = -1; l <= 1; ++l) { ++ for (int i1 = -1; i1 <= 1; ++i1) { ++ long j1 = SectionPosition.a(i, k, l, i1); ++ ++ if (j1 != i) { ++ this.b(i, j1, j, flag); ++ } ++ } ++ } ++ } ++ ++ } ++ ++ @Override ++ protected int a(long i, long j, int k) { ++ int l = k; ++ ++ for (int i1 = -1; i1 <= 1; ++i1) { ++ for (int j1 = -1; j1 <= 1; ++j1) { ++ for (int k1 = -1; k1 <= 1; ++k1) { ++ long l1 = SectionPosition.a(i, i1, j1, k1); ++ ++ if (l1 == i) { ++ l1 = Long.MAX_VALUE; ++ } ++ ++ if (l1 != j) { ++ int i2 = this.b(l1, i, this.c(l1)); ++ ++ if (l > i2) { ++ l = i2; ++ } ++ ++ if (l == 0) { ++ return l; ++ } ++ } ++ } ++ } ++ } ++ ++ return l; ++ } ++ ++ @Override ++ protected int b(long i, long j, int k) { ++ return i == Long.MAX_VALUE ? this.b(j) : k + 1; ++ } ++ ++ protected abstract int b(long i); ++ ++ public void b(long i, int j, boolean flag) { ++ this.a(Long.MAX_VALUE, i, j, flag); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/LightEngineThreaded.java b/src/main/java/net/minecraft/server/level/LightEngineThreaded.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5a51f47f747382ec2a30bb47bcb1f7c61dd4c369 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/level/LightEngineThreaded.java +@@ -0,0 +1,228 @@ ++package net.minecraft.server.level; ++ ++import com.mojang.datafixers.util.Pair; ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; ++import it.unimi.dsi.fastutil.objects.ObjectList; ++import it.unimi.dsi.fastutil.objects.ObjectListIterator; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.function.IntSupplier; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.util.thread.Mailbox; ++import net.minecraft.util.thread.ThreadedMailbox; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.chunk.ChunkSection; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.chunk.ILightAccess; ++import net.minecraft.world.level.chunk.NibbleArray; ++import net.minecraft.world.level.lighting.LightEngine; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class LightEngineThreaded extends LightEngine implements AutoCloseable { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final ThreadedMailbox b; ++ private final ObjectList> c = new ObjectArrayList(); ++ private final PlayerChunkMap d; ++ private final Mailbox> e; ++ private volatile int f = 5; ++ private final AtomicBoolean g = new AtomicBoolean(); ++ ++ public LightEngineThreaded(ILightAccess ilightaccess, PlayerChunkMap playerchunkmap, boolean flag, ThreadedMailbox threadedmailbox, Mailbox> mailbox) { ++ super(ilightaccess, true, flag); ++ this.d = playerchunkmap; ++ this.e = mailbox; ++ this.b = threadedmailbox; ++ } ++ ++ public void close() {} ++ ++ @Override ++ public int a(int i, boolean flag, boolean flag1) { ++ throw (UnsupportedOperationException) SystemUtils.c((Throwable) (new UnsupportedOperationException("Ran authomatically on a different thread!"))); ++ } ++ ++ @Override ++ public void a(BlockPosition blockposition, int i) { ++ throw (UnsupportedOperationException) SystemUtils.c((Throwable) (new UnsupportedOperationException("Ran authomatically on a different thread!"))); ++ } ++ ++ @Override ++ public void a(BlockPosition blockposition) { ++ BlockPosition blockposition1 = blockposition.immutableCopy(); ++ ++ this.a(blockposition.getX() >> 4, blockposition.getZ() >> 4, LightEngineThreaded.Update.POST_UPDATE, SystemUtils.a(() -> { ++ super.a(blockposition1); ++ }, () -> { ++ return "checkBlock " + blockposition1; ++ })); ++ } ++ ++ protected void a(ChunkCoordIntPair chunkcoordintpair) { ++ this.a(chunkcoordintpair.x, chunkcoordintpair.z, () -> { ++ return 0; ++ }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { ++ super.b(chunkcoordintpair, false); ++ super.a(chunkcoordintpair, false); ++ ++ int i; ++ ++ for (i = -1; i < 17; ++i) { ++ super.a(EnumSkyBlock.BLOCK, SectionPosition.a(chunkcoordintpair, i), (NibbleArray) null, true); ++ super.a(EnumSkyBlock.SKY, SectionPosition.a(chunkcoordintpair, i), (NibbleArray) null, true); ++ } ++ ++ for (i = 0; i < 16; ++i) { ++ super.a(SectionPosition.a(chunkcoordintpair, i), true); ++ } ++ ++ }, () -> { ++ return "updateChunkStatus " + chunkcoordintpair + " " + true; ++ })); ++ } ++ ++ @Override ++ public void a(SectionPosition sectionposition, boolean flag) { ++ this.a(sectionposition.a(), sectionposition.c(), () -> { ++ return 0; ++ }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { ++ super.a(sectionposition, flag); ++ }, () -> { ++ return "updateSectionStatus " + sectionposition + " " + flag; ++ })); ++ } ++ ++ @Override ++ public void a(ChunkCoordIntPair chunkcoordintpair, boolean flag) { ++ this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { ++ super.a(chunkcoordintpair, flag); ++ }, () -> { ++ return "enableLight " + chunkcoordintpair + " " + flag; ++ })); ++ } ++ ++ @Override ++ public void a(EnumSkyBlock enumskyblock, SectionPosition sectionposition, @Nullable NibbleArray nibblearray, boolean flag) { ++ this.a(sectionposition.a(), sectionposition.c(), () -> { ++ return 0; ++ }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { ++ super.a(enumskyblock, sectionposition, nibblearray, flag); ++ }, () -> { ++ return "queueData " + sectionposition; ++ })); ++ } ++ ++ private void a(int i, int j, LightEngineThreaded.Update lightenginethreaded_update, Runnable runnable) { ++ this.a(i, j, this.d.c(ChunkCoordIntPair.pair(i, j)), lightenginethreaded_update, runnable); ++ } ++ ++ private void a(int i, int j, IntSupplier intsupplier, LightEngineThreaded.Update lightenginethreaded_update, Runnable runnable) { ++ this.e.a(ChunkTaskQueueSorter.a(() -> { ++ this.c.add(Pair.of(lightenginethreaded_update, runnable)); ++ if (this.c.size() >= this.f) { ++ this.b(); ++ } ++ ++ }, ChunkCoordIntPair.pair(i, j), intsupplier)); ++ } ++ ++ @Override ++ public void b(ChunkCoordIntPair chunkcoordintpair, boolean flag) { ++ this.a(chunkcoordintpair.x, chunkcoordintpair.z, () -> { ++ return 0; ++ }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { ++ super.b(chunkcoordintpair, flag); ++ }, () -> { ++ return "retainData " + chunkcoordintpair; ++ })); ++ } ++ ++ public CompletableFuture a(IChunkAccess ichunkaccess, boolean flag) { ++ ChunkCoordIntPair chunkcoordintpair = ichunkaccess.getPos(); ++ ++ ichunkaccess.b(false); ++ this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { ++ ChunkSection[] achunksection = ichunkaccess.getSections(); ++ ++ for (int i = 0; i < 16; ++i) { ++ ChunkSection chunksection = achunksection[i]; ++ ++ if (!ChunkSection.a(chunksection)) { ++ super.a(SectionPosition.a(chunkcoordintpair, i), false); ++ } ++ } ++ ++ super.a(chunkcoordintpair, true); ++ if (!flag) { ++ ichunkaccess.m().forEach((blockposition) -> { ++ super.a(blockposition, ichunkaccess.g(blockposition)); ++ }); ++ } ++ ++ this.d.c(chunkcoordintpair); ++ }, () -> { ++ return "lightChunk " + chunkcoordintpair + " " + flag; ++ })); ++ return CompletableFuture.supplyAsync(() -> { ++ ichunkaccess.b(true); ++ super.b(chunkcoordintpair, false); ++ return ichunkaccess; ++ }, (runnable) -> { ++ this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.POST_UPDATE, runnable); ++ }); ++ } ++ ++ public void queueUpdate() { ++ if ((!this.c.isEmpty() || super.a()) && this.g.compareAndSet(false, true)) { ++ this.b.a((Object) (() -> { ++ this.b(); ++ this.g.set(false); ++ })); ++ } ++ ++ } ++ ++ private void b() { ++ int i = Math.min(this.c.size(), this.f); ++ ObjectListIterator> objectlistiterator = this.c.iterator(); ++ ++ Pair pair; ++ int j; ++ ++ for (j = 0; objectlistiterator.hasNext() && j < i; ++j) { ++ pair = (Pair) objectlistiterator.next(); ++ if (pair.getFirst() == LightEngineThreaded.Update.PRE_UPDATE) { ++ ((Runnable) pair.getSecond()).run(); ++ } ++ } ++ ++ objectlistiterator.back(j); ++ super.a(Integer.MAX_VALUE, true, true); ++ ++ for (j = 0; objectlistiterator.hasNext() && j < i; ++j) { ++ pair = (Pair) objectlistiterator.next(); ++ if (pair.getFirst() == LightEngineThreaded.Update.POST_UPDATE) { ++ ((Runnable) pair.getSecond()).run(); ++ } ++ ++ objectlistiterator.remove(); ++ } ++ ++ } ++ ++ public void a(int i) { ++ this.f = i; ++ } ++ ++ static enum Update { ++ ++ PRE_UPDATE, POST_UPDATE; ++ ++ private Update() {} ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1c500e1193296f92f03a94e2cf085b215daaad6c +--- /dev/null ++++ b/src/main/java/net/minecraft/server/level/Ticket.java +@@ -0,0 +1,67 @@ ++package net.minecraft.server.level; ++ ++import java.util.Objects; ++ ++public final class Ticket implements Comparable> { ++ ++ private final TicketType a; ++ private final int b; ++ public final T identifier; ++ private long d; ++ ++ protected Ticket(TicketType tickettype, int i, T t0) { ++ this.a = tickettype; ++ this.b = i; ++ this.identifier = t0; ++ } ++ ++ public int compareTo(Ticket ticket) { ++ int i = Integer.compare(this.b, ticket.b); ++ ++ if (i != 0) { ++ return i; ++ } else { ++ int j = Integer.compare(System.identityHashCode(this.a), System.identityHashCode(ticket.a)); ++ ++ return j != 0 ? j : this.a.a().compare(this.identifier, ticket.identifier); ++ } ++ } ++ ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } else if (!(object instanceof Ticket)) { ++ return false; ++ } else { ++ Ticket ticket = (Ticket) object; ++ ++ return this.b == ticket.b && Objects.equals(this.a, ticket.a) && Objects.equals(this.identifier, ticket.identifier); ++ } ++ } ++ ++ public int hashCode() { ++ return Objects.hash(new Object[]{this.a, this.b, this.identifier}); ++ } ++ ++ public String toString() { ++ return "Ticket[" + this.a + " " + this.b + " (" + this.identifier + ")] at " + this.d; ++ } ++ ++ public TicketType getTicketType() { ++ return this.a; ++ } ++ ++ public int b() { ++ return this.b; ++ } ++ ++ protected void a(long i) { ++ this.d = i; ++ } ++ ++ protected boolean b(long i) { ++ long j = this.a.b(); ++ ++ return j != 0L && i - this.d > j; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/progress/WorldLoadListener.java b/src/main/java/net/minecraft/server/level/progress/WorldLoadListener.java +new file mode 100644 +index 0000000000000000000000000000000000000000..de011b5e3a5e751160b4d3b65b50f28e6c6a5f52 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/level/progress/WorldLoadListener.java +@@ -0,0 +1,14 @@ ++package net.minecraft.server.level.progress; ++ ++import javax.annotation.Nullable; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.chunk.ChunkStatus; ++ ++public interface WorldLoadListener { ++ ++ void a(ChunkCoordIntPair chunkcoordintpair); ++ ++ void a(ChunkCoordIntPair chunkcoordintpair, @Nullable ChunkStatus chunkstatus); ++ ++ void b(); ++} +diff --git a/src/main/java/net/minecraft/server/level/progress/WorldLoadListenerLogger.java b/src/main/java/net/minecraft/server/level/progress/WorldLoadListenerLogger.java +new file mode 100644 +index 0000000000000000000000000000000000000000..872d00de41533ab7f4b43874de6c1747022e2ac5 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/level/progress/WorldLoadListenerLogger.java +@@ -0,0 +1,56 @@ ++package net.minecraft.server.level.progress; ++ ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.network.chat.ChatMessage; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class WorldLoadListenerLogger implements WorldLoadListener { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final int b; ++ private int c; ++ private long d; ++ private long e = Long.MAX_VALUE; ++ ++ public WorldLoadListenerLogger(int i) { ++ int j = i * 2 + 1; ++ ++ this.b = j * j; ++ } ++ ++ @Override ++ public void a(ChunkCoordIntPair chunkcoordintpair) { ++ this.e = SystemUtils.getMonotonicMillis(); ++ this.d = this.e; ++ } ++ ++ @Override ++ public void a(ChunkCoordIntPair chunkcoordintpair, @Nullable ChunkStatus chunkstatus) { ++ if (chunkstatus == ChunkStatus.FULL) { ++ ++this.c; ++ } ++ ++ int i = this.c(); ++ ++ if (SystemUtils.getMonotonicMillis() > this.e) { ++ this.e += 500L; ++ WorldLoadListenerLogger.LOGGER.info((new ChatMessage("menu.preparingSpawn", new Object[]{MathHelper.clamp(i, 0, 100)})).getString()); ++ } ++ ++ } ++ ++ @Override ++ public void b() { ++ WorldLoadListenerLogger.LOGGER.info("Time elapsed: {} ms", SystemUtils.getMonotonicMillis() - this.d); ++ this.e = Long.MAX_VALUE; ++ } ++ ++ public int c() { ++ return MathHelper.d((float) this.c * 100.0F / (float) this.b); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/rcon/RemoteStatusReply.java b/src/main/java/net/minecraft/server/rcon/RemoteStatusReply.java +new file mode 100644 +index 0000000000000000000000000000000000000000..57ff3db0a0199ef03045b880e598407886b0306b +--- /dev/null ++++ b/src/main/java/net/minecraft/server/rcon/RemoteStatusReply.java +@@ -0,0 +1,41 @@ ++package net.minecraft.server.rcon; ++ ++import java.io.ByteArrayOutputStream; ++import java.io.DataOutputStream; ++import java.io.IOException; ++ ++public class RemoteStatusReply { ++ ++ private final ByteArrayOutputStream a; ++ private final DataOutputStream b; ++ ++ public RemoteStatusReply(int i) { ++ this.a = new ByteArrayOutputStream(i); ++ this.b = new DataOutputStream(this.a); ++ } ++ ++ public void a(byte[] abyte) throws IOException { ++ this.b.write(abyte, 0, abyte.length); ++ } ++ ++ public void a(String s) throws IOException { ++ this.b.writeBytes(s); ++ this.b.write(0); ++ } ++ ++ public void a(int i) throws IOException { ++ this.b.write(i); ++ } ++ ++ public void a(short short0) throws IOException { ++ this.b.writeShort(Short.reverseBytes(short0)); ++ } ++ ++ public byte[] a() { ++ return this.a.toByteArray(); ++ } ++ ++ public void b() { ++ this.a.reset(); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/rcon/thread/RemoteControlListener.java b/src/main/java/net/minecraft/server/rcon/thread/RemoteControlListener.java +new file mode 100644 +index 0000000000000000000000000000000000000000..797a450a08da1b799e32fae2a71a7a50bb90d127 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/rcon/thread/RemoteControlListener.java +@@ -0,0 +1,131 @@ ++package net.minecraft.server.rcon.thread; ++ ++import com.google.common.collect.Lists; ++import java.io.IOException; ++import java.net.InetAddress; ++import java.net.ServerSocket; ++import java.net.Socket; ++import java.net.SocketTimeoutException; ++import java.util.Iterator; ++import java.util.List; ++import javax.annotation.Nullable; ++import net.minecraft.server.IMinecraftServer; ++import net.minecraft.server.dedicated.DedicatedServerProperties; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class RemoteControlListener extends RemoteConnectionThread { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final ServerSocket e; ++ private final String f; ++ private final List g = Lists.newArrayList(); ++ private final IMinecraftServer h; ++ ++ private RemoteControlListener(IMinecraftServer iminecraftserver, ServerSocket serversocket, String s) { ++ super("RCON Listener"); ++ this.h = iminecraftserver; ++ this.e = serversocket; ++ this.f = s; ++ } ++ ++ private void d() { ++ this.g.removeIf((remotecontrolsession) -> { ++ return !remotecontrolsession.c(); ++ }); ++ } ++ ++ public void run() { ++ try { ++ while (this.a) { ++ try { ++ Socket socket = this.e.accept(); ++ RemoteControlSession remotecontrolsession = new RemoteControlSession(this.h, this.f, socket); ++ ++ remotecontrolsession.a(); ++ this.g.add(remotecontrolsession); ++ this.d(); ++ } catch (SocketTimeoutException sockettimeoutexception) { ++ this.d(); ++ } catch (IOException ioexception) { ++ if (this.a) { ++ RemoteControlListener.LOGGER.info("IO exception: ", ioexception); ++ } ++ } ++ } ++ } finally { ++ this.a(this.e); ++ } ++ ++ } ++ ++ @Nullable ++ public static RemoteControlListener a(IMinecraftServer iminecraftserver) { ++ DedicatedServerProperties dedicatedserverproperties = iminecraftserver.getDedicatedServerProperties(); ++ String s = iminecraftserver.h_(); ++ ++ if (s.isEmpty()) { ++ s = "0.0.0.0"; ++ } ++ ++ int i = dedicatedserverproperties.rconPort; ++ ++ if (0 < i && 65535 >= i) { ++ String s1 = dedicatedserverproperties.rconPassword; ++ ++ if (s1.isEmpty()) { ++ RemoteControlListener.LOGGER.warn("No rcon password set in server.properties, rcon disabled!"); ++ return null; ++ } else { ++ try { ++ ServerSocket serversocket = new ServerSocket(i, 0, InetAddress.getByName(s)); ++ ++ serversocket.setSoTimeout(500); ++ RemoteControlListener remotecontrollistener = new RemoteControlListener(iminecraftserver, serversocket, s1); ++ ++ if (!remotecontrollistener.a()) { ++ return null; ++ } else { ++ RemoteControlListener.LOGGER.info("RCON running on {}:{}", s, i); ++ return remotecontrollistener; ++ } ++ } catch (IOException ioexception) { ++ RemoteControlListener.LOGGER.warn("Unable to initialise RCON on {}:{}", s, i, ioexception); ++ return null; ++ } ++ } ++ } else { ++ RemoteControlListener.LOGGER.warn("Invalid rcon port {} found in server.properties, rcon disabled!", i); ++ return null; ++ } ++ } ++ ++ @Override ++ public void b() { ++ this.a = false; ++ this.a(this.e); ++ super.b(); ++ Iterator iterator = this.g.iterator(); ++ ++ while (iterator.hasNext()) { ++ RemoteControlSession remotecontrolsession = (RemoteControlSession) iterator.next(); ++ ++ if (remotecontrolsession.c()) { ++ remotecontrolsession.b(); ++ } ++ } ++ ++ this.g.clear(); ++ } ++ ++ private void a(ServerSocket serversocket) { ++ RemoteControlListener.LOGGER.debug("closeSocket: {}", serversocket); ++ ++ try { ++ serversocket.close(); ++ } catch (IOException ioexception) { ++ RemoteControlListener.LOGGER.warn("Failed to close socket", ioexception); ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/server/rcon/thread/RemoteStatusListener.java b/src/main/java/net/minecraft/server/rcon/thread/RemoteStatusListener.java +new file mode 100644 +index 0000000000000000000000000000000000000000..55b379af2e0c8c3513a76a346d381cd3dbcabe40 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/rcon/thread/RemoteStatusListener.java +@@ -0,0 +1,318 @@ ++package net.minecraft.server.rcon.thread; ++ ++import com.google.common.collect.Maps; ++import java.io.IOException; ++import java.net.DatagramPacket; ++import java.net.DatagramSocket; ++import java.net.InetAddress; ++import java.net.PortUnreachableException; ++import java.net.SocketAddress; ++import java.net.SocketTimeoutException; ++import java.net.UnknownHostException; ++import java.nio.charset.StandardCharsets; ++import java.util.Date; ++import java.util.Map; ++import java.util.Random; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.server.IMinecraftServer; ++import net.minecraft.server.rcon.RemoteStatusReply; ++import net.minecraft.server.rcon.StatusChallengeUtils; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class RemoteStatusListener extends RemoteConnectionThread { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private long e; ++ private final int f; ++ private final int g; ++ private final int h; ++ private final String i; ++ private final String j; ++ private DatagramSocket k; ++ private final byte[] l = new byte[1460]; ++ private String m; ++ private String n; ++ private final Map o; ++ private final RemoteStatusReply p; ++ private long q; ++ private final IMinecraftServer r; ++ ++ private RemoteStatusListener(IMinecraftServer iminecraftserver, int i) { ++ super("Query Listener"); ++ this.r = iminecraftserver; ++ this.f = i; ++ this.n = iminecraftserver.h_(); ++ this.g = iminecraftserver.p(); ++ this.i = iminecraftserver.i_(); ++ this.h = iminecraftserver.getMaxPlayers(); ++ this.j = iminecraftserver.getWorld(); ++ this.q = 0L; ++ this.m = "0.0.0.0"; ++ if (!this.n.isEmpty() && !this.m.equals(this.n)) { ++ this.m = this.n; ++ } else { ++ this.n = "0.0.0.0"; ++ ++ try { ++ InetAddress inetaddress = InetAddress.getLocalHost(); ++ ++ this.m = inetaddress.getHostAddress(); ++ } catch (UnknownHostException unknownhostexception) { ++ RemoteStatusListener.LOGGER.warn("Unable to determine local host IP, please set server-ip in server.properties", unknownhostexception); ++ } ++ } ++ ++ this.p = new RemoteStatusReply(1460); ++ this.o = Maps.newHashMap(); ++ } ++ ++ @Nullable ++ public static RemoteStatusListener a(IMinecraftServer iminecraftserver) { ++ int i = iminecraftserver.getDedicatedServerProperties().queryPort; ++ ++ if (0 < i && 65535 >= i) { ++ RemoteStatusListener remotestatuslistener = new RemoteStatusListener(iminecraftserver, i); ++ ++ return !remotestatuslistener.a() ? null : remotestatuslistener; ++ } else { ++ RemoteStatusListener.LOGGER.warn("Invalid query port {} found in server.properties (queries disabled)", i); ++ return null; ++ } ++ } ++ ++ private void a(byte[] abyte, DatagramPacket datagrampacket) throws IOException { ++ this.k.send(new DatagramPacket(abyte, abyte.length, datagrampacket.getSocketAddress())); ++ } ++ ++ private boolean a(DatagramPacket datagrampacket) throws IOException { ++ byte[] abyte = datagrampacket.getData(); ++ int i = datagrampacket.getLength(); ++ SocketAddress socketaddress = datagrampacket.getSocketAddress(); ++ ++ RemoteStatusListener.LOGGER.debug("Packet len {} [{}]", i, socketaddress); ++ if (3 <= i && -2 == abyte[0] && -3 == abyte[1]) { ++ RemoteStatusListener.LOGGER.debug("Packet '{}' [{}]", StatusChallengeUtils.a(abyte[2]), socketaddress); ++ switch (abyte[2]) { ++ case 0: ++ if (!this.c(datagrampacket)) { ++ RemoteStatusListener.LOGGER.debug("Invalid challenge [{}]", socketaddress); ++ return false; ++ } else if (15 == i) { ++ this.a(this.b(datagrampacket), datagrampacket); ++ RemoteStatusListener.LOGGER.debug("Rules [{}]", socketaddress); ++ } else { ++ RemoteStatusReply remotestatusreply = new RemoteStatusReply(1460); ++ ++ remotestatusreply.a((int) 0); ++ remotestatusreply.a(this.a(datagrampacket.getSocketAddress())); ++ remotestatusreply.a(this.i); ++ remotestatusreply.a("SMP"); ++ remotestatusreply.a(this.j); ++ remotestatusreply.a(Integer.toString(this.r.getPlayerCount())); ++ remotestatusreply.a(Integer.toString(this.h)); ++ remotestatusreply.a((short) this.g); ++ remotestatusreply.a(this.m); ++ this.a(remotestatusreply.a(), datagrampacket); ++ RemoteStatusListener.LOGGER.debug("Status [{}]", socketaddress); ++ } ++ default: ++ return true; ++ case 9: ++ this.d(datagrampacket); ++ RemoteStatusListener.LOGGER.debug("Challenge [{}]", socketaddress); ++ return true; ++ } ++ } else { ++ RemoteStatusListener.LOGGER.debug("Invalid packet [{}]", socketaddress); ++ return false; ++ } ++ } ++ ++ private byte[] b(DatagramPacket datagrampacket) throws IOException { ++ long i = SystemUtils.getMonotonicMillis(); ++ ++ if (i < this.q + 5000L) { ++ byte[] abyte = this.p.a(); ++ byte[] abyte1 = this.a(datagrampacket.getSocketAddress()); ++ ++ abyte[1] = abyte1[0]; ++ abyte[2] = abyte1[1]; ++ abyte[3] = abyte1[2]; ++ abyte[4] = abyte1[3]; ++ return abyte; ++ } else { ++ this.q = i; ++ this.p.b(); ++ this.p.a((int) 0); ++ this.p.a(this.a(datagrampacket.getSocketAddress())); ++ this.p.a("splitnum"); ++ this.p.a((int) 128); ++ this.p.a((int) 0); ++ this.p.a("hostname"); ++ this.p.a(this.i); ++ this.p.a("gametype"); ++ this.p.a("SMP"); ++ this.p.a("game_id"); ++ this.p.a("MINECRAFT"); ++ this.p.a("version"); ++ this.p.a(this.r.getVersion()); ++ this.p.a("plugins"); ++ this.p.a(this.r.getPlugins()); ++ this.p.a("map"); ++ this.p.a(this.j); ++ this.p.a("numplayers"); ++ this.p.a("" + this.r.getPlayerCount()); ++ this.p.a("maxplayers"); ++ this.p.a("" + this.h); ++ this.p.a("hostport"); ++ this.p.a("" + this.g); ++ this.p.a("hostip"); ++ this.p.a(this.m); ++ this.p.a((int) 0); ++ this.p.a((int) 1); ++ this.p.a("player_"); ++ this.p.a((int) 0); ++ String[] astring = this.r.getPlayers(); ++ String[] astring1 = astring; ++ int j = astring.length; ++ ++ for (int k = 0; k < j; ++k) { ++ String s = astring1[k]; ++ ++ this.p.a(s); ++ } ++ ++ this.p.a((int) 0); ++ return this.p.a(); ++ } ++ } ++ ++ private byte[] a(SocketAddress socketaddress) { ++ return ((RemoteStatusListener.RemoteStatusChallenge) this.o.get(socketaddress)).c(); ++ } ++ ++ private Boolean c(DatagramPacket datagrampacket) { ++ SocketAddress socketaddress = datagrampacket.getSocketAddress(); ++ ++ if (!this.o.containsKey(socketaddress)) { ++ return false; ++ } else { ++ byte[] abyte = datagrampacket.getData(); ++ ++ return ((RemoteStatusListener.RemoteStatusChallenge) this.o.get(socketaddress)).a() == StatusChallengeUtils.c(abyte, 7, datagrampacket.getLength()); ++ } ++ } ++ ++ private void d(DatagramPacket datagrampacket) throws IOException { ++ RemoteStatusListener.RemoteStatusChallenge remotestatuslistener_remotestatuschallenge = new RemoteStatusListener.RemoteStatusChallenge(datagrampacket); ++ ++ this.o.put(datagrampacket.getSocketAddress(), remotestatuslistener_remotestatuschallenge); ++ this.a(remotestatuslistener_remotestatuschallenge.b(), datagrampacket); ++ } ++ ++ private void d() { ++ if (this.a) { ++ long i = SystemUtils.getMonotonicMillis(); ++ ++ if (i >= this.e + 30000L) { ++ this.e = i; ++ this.o.values().removeIf((remotestatuslistener_remotestatuschallenge) -> { ++ return remotestatuslistener_remotestatuschallenge.a(i); ++ }); ++ } ++ } ++ } ++ ++ public void run() { ++ RemoteStatusListener.LOGGER.info("Query running on {}:{}", this.n, this.f); ++ this.e = SystemUtils.getMonotonicMillis(); ++ DatagramPacket datagrampacket = new DatagramPacket(this.l, this.l.length); ++ ++ try { ++ while (this.a) { ++ try { ++ this.k.receive(datagrampacket); ++ this.d(); ++ this.a(datagrampacket); ++ } catch (SocketTimeoutException sockettimeoutexception) { ++ this.d(); ++ } catch (PortUnreachableException portunreachableexception) { ++ ; ++ } catch (IOException ioexception) { ++ this.a((Exception) ioexception); ++ } ++ } ++ } finally { ++ RemoteStatusListener.LOGGER.debug("closeSocket: {}:{}", this.n, this.f); ++ this.k.close(); ++ } ++ ++ } ++ ++ @Override ++ public boolean a() { ++ return this.a ? true : (!this.e() ? false : super.a()); ++ } ++ ++ private void a(Exception exception) { ++ if (this.a) { ++ RemoteStatusListener.LOGGER.warn("Unexpected exception", exception); ++ if (!this.e()) { ++ RemoteStatusListener.LOGGER.error("Failed to recover from exception, shutting down!"); ++ this.a = false; ++ } ++ ++ } ++ } ++ ++ private boolean e() { ++ try { ++ this.k = new DatagramSocket(this.f, InetAddress.getByName(this.n)); ++ this.k.setSoTimeout(500); ++ return true; ++ } catch (Exception exception) { ++ RemoteStatusListener.LOGGER.warn("Unable to initialise query system on {}:{}", this.n, this.f, exception); ++ return false; ++ } ++ } ++ ++ static class RemoteStatusChallenge { ++ ++ private final long time = (new Date()).getTime(); ++ private final int token; ++ private final byte[] identity; ++ private final byte[] d; ++ private final String e; ++ ++ public RemoteStatusChallenge(DatagramPacket datagrampacket) { ++ byte[] abyte = datagrampacket.getData(); ++ ++ this.identity = new byte[4]; ++ this.identity[0] = abyte[3]; ++ this.identity[1] = abyte[4]; ++ this.identity[2] = abyte[5]; ++ this.identity[3] = abyte[6]; ++ this.e = new String(this.identity, StandardCharsets.UTF_8); ++ this.token = (new Random()).nextInt(16777216); ++ this.d = String.format("\t%s%d\u0000", this.e, this.token).getBytes(StandardCharsets.UTF_8); ++ } ++ ++ public Boolean a(long i) { ++ return this.time < i; ++ } ++ ++ public int a() { ++ return this.token; ++ } ++ ++ public byte[] b() { ++ return this.d; ++ } ++ ++ public byte[] c() { ++ return this.identity; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/stats/StatisticWrapper.java b/src/main/java/net/minecraft/stats/StatisticWrapper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c1a694c4a773a41cdefca6b154711f7fc0a7fcaa +--- /dev/null ++++ b/src/main/java/net/minecraft/stats/StatisticWrapper.java +@@ -0,0 +1,34 @@ ++package net.minecraft.stats; ++ ++import java.util.IdentityHashMap; ++import java.util.Iterator; ++import java.util.Map; ++import net.minecraft.core.IRegistry; ++ ++public class StatisticWrapper implements Iterable> { ++ ++ private final IRegistry a; ++ private final Map> b = new IdentityHashMap(); ++ ++ public StatisticWrapper(IRegistry iregistry) { ++ this.a = iregistry; ++ } ++ ++ public Statistic a(T t0, Counter counter) { ++ return (Statistic) this.b.computeIfAbsent(t0, (object) -> { ++ return new Statistic<>(this, object, counter); ++ }); ++ } ++ ++ public IRegistry getRegistry() { ++ return this.a; ++ } ++ ++ public Iterator> iterator() { ++ return this.b.values().iterator(); ++ } ++ ++ public Statistic b(T t0) { ++ return this.a(t0, Counter.DEFAULT); ++ } ++} +diff --git a/src/main/java/net/minecraft/util/ArraySetSorted.java b/src/main/java/net/minecraft/util/ArraySetSorted.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e56b8e172d96c5508457fcf3f5a0cf0d2d2d8d7c +--- /dev/null ++++ b/src/main/java/net/minecraft/util/ArraySetSorted.java +@@ -0,0 +1,202 @@ ++package net.minecraft.util; ++ ++import it.unimi.dsi.fastutil.objects.ObjectArrays; ++import java.util.AbstractSet; ++import java.util.Arrays; ++import java.util.Comparator; ++import java.util.Iterator; ++import java.util.NoSuchElementException; ++ ++public class ArraySetSorted extends AbstractSet { ++ ++ private final Comparator a; ++ private T[] b; ++ private int c; ++ ++ private ArraySetSorted(int i, Comparator comparator) { ++ this.a = comparator; ++ if (i < 0) { ++ throw new IllegalArgumentException("Initial capacity (" + i + ") is negative"); ++ } else { ++ this.b = a(new Object[i]); ++ } ++ } ++ ++ public static > ArraySetSorted a(int i) { ++ return new ArraySetSorted<>(i, Comparator.naturalOrder()); ++ } ++ ++ private static T[] a(Object[] aobject) { ++ return (Object[]) aobject; ++ } ++ ++ private int c(T t0) { ++ return Arrays.binarySearch(this.b, 0, this.c, t0, this.a); ++ } ++ ++ private static int b(int i) { ++ return -i - 1; ++ } ++ ++ public boolean add(T t0) { ++ int i = this.c(t0); ++ ++ if (i >= 0) { ++ return false; ++ } else { ++ int j = b(i); ++ ++ this.a(t0, j); ++ return true; ++ } ++ } ++ ++ private void c(int i) { ++ if (i > this.b.length) { ++ if (this.b != ObjectArrays.DEFAULT_EMPTY_ARRAY) { ++ i = (int) Math.max(Math.min((long) this.b.length + (long) (this.b.length >> 1), 2147483639L), (long) i); ++ } else if (i < 10) { ++ i = 10; ++ } ++ ++ Object[] aobject = new Object[i]; ++ ++ System.arraycopy(this.b, 0, aobject, 0, this.c); ++ this.b = a(aobject); ++ } ++ } ++ ++ private void a(T t0, int i) { ++ this.c(this.c + 1); ++ if (i != this.c) { ++ System.arraycopy(this.b, i, this.b, i + 1, this.c - i); ++ } ++ ++ this.b[i] = t0; ++ ++this.c; ++ } ++ ++ private void d(int i) { ++ --this.c; ++ if (i != this.c) { ++ System.arraycopy(this.b, i + 1, this.b, i, this.c - i); ++ } ++ ++ this.b[this.c] = null; ++ } ++ ++ private T e(int i) { ++ return this.b[i]; ++ } ++ ++ public T a(T t0) { ++ int i = this.c(t0); ++ ++ if (i >= 0) { ++ return this.e(i); ++ } else { ++ this.a(t0, b(i)); ++ return t0; ++ } ++ } ++ ++ public boolean remove(Object object) { ++ int i = this.c(object); ++ ++ if (i >= 0) { ++ this.d(i); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ public T b() { ++ return this.e(0); ++ } ++ ++ public boolean contains(Object object) { ++ int i = this.c(object); ++ ++ return i >= 0; ++ } ++ ++ public Iterator iterator() { ++ return new ArraySetSorted.a(); ++ } ++ ++ public int size() { ++ return this.c; ++ } ++ ++ public Object[] toArray() { ++ return (Object[]) this.b.clone(); ++ } ++ ++ public U[] toArray(U[] au) { ++ if (au.length < this.c) { ++ return (Object[]) Arrays.copyOf(this.b, this.c, au.getClass()); ++ } else { ++ System.arraycopy(this.b, 0, au, 0, this.c); ++ if (au.length > this.c) { ++ au[this.c] = null; ++ } ++ ++ return au; ++ } ++ } ++ ++ public void clear() { ++ Arrays.fill(this.b, 0, this.c, (Object) null); ++ this.c = 0; ++ } ++ ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } else { ++ if (object instanceof ArraySetSorted) { ++ ArraySetSorted arraysetsorted = (ArraySetSorted) object; ++ ++ if (this.a.equals(arraysetsorted.a)) { ++ return this.c == arraysetsorted.c && Arrays.equals(this.b, arraysetsorted.b); ++ } ++ } ++ ++ return super.equals(object); ++ } ++ } ++ ++ class a implements Iterator { ++ ++ private int b; ++ private int c; ++ ++ private a() { ++ this.c = -1; ++ } ++ ++ public boolean hasNext() { ++ return this.b < ArraySetSorted.this.c; ++ } ++ ++ public T next() { ++ if (this.b >= ArraySetSorted.this.c) { ++ throw new NoSuchElementException(); ++ } else { ++ this.c = this.b++; ++ return ArraySetSorted.this.b[this.c]; ++ } ++ } ++ ++ public void remove() { ++ if (this.c == -1) { ++ throw new IllegalStateException(); ++ } else { ++ ArraySetSorted.this.d(this.c); ++ --this.b; ++ this.c = -1; ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/util/DataBits.java b/src/main/java/net/minecraft/util/DataBits.java +new file mode 100644 +index 0000000000000000000000000000000000000000..54974a941a334dc0c8e62ffb8ca094772888b8fa +--- /dev/null ++++ b/src/main/java/net/minecraft/util/DataBits.java +@@ -0,0 +1,114 @@ ++package net.minecraft.util; ++ ++import java.util.function.IntConsumer; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import org.apache.commons.lang3.Validate; ++ ++public class DataBits { ++ ++ private static final int[] a = new int[]{-1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE, 0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756, 0, Integer.MIN_VALUE, 0, 2, 477218588, 477218588, 0, 429496729, 429496729, 0, 390451572, 390451572, 0, 357913941, 357913941, 0, 330382099, 330382099, 0, 306783378, 306783378, 0, 286331153, 286331153, 0, Integer.MIN_VALUE, 0, 3, 252645135, 252645135, 0, 238609294, 238609294, 0, 226050910, 226050910, 0, 214748364, 214748364, 0, 204522252, 204522252, 0, 195225786, 195225786, 0, 186737708, 186737708, 0, 178956970, 178956970, 0, 171798691, 171798691, 0, 165191049, 165191049, 0, 159072862, 159072862, 0, 153391689, 153391689, 0, 148102320, 148102320, 0, 143165576, 143165576, 0, 138547332, 138547332, 0, Integer.MIN_VALUE, 0, 4, 130150524, 130150524, 0, 126322567, 126322567, 0, 122713351, 122713351, 0, 119304647, 119304647, 0, 116080197, 116080197, 0, 113025455, 113025455, 0, 110127366, 110127366, 0, 107374182, 107374182, 0, 104755299, 104755299, 0, 102261126, 102261126, 0, 99882960, 99882960, 0, 97612893, 97612893, 0, 95443717, 95443717, 0, 93368854, 93368854, 0, 91382282, 91382282, 0, 89478485, 89478485, 0, 87652393, 87652393, 0, 85899345, 85899345, 0, 84215045, 84215045, 0, 82595524, 82595524, 0, 81037118, 81037118, 0, 79536431, 79536431, 0, 78090314, 78090314, 0, 76695844, 76695844, 0, 75350303, 75350303, 0, 74051160, 74051160, 0, 72796055, 72796055, 0, 71582788, 71582788, 0, 70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE, 0, 5}; ++ private final long[] b; ++ private final int c; ++ private final long d; ++ private final int e; ++ private final int f; ++ private final int g; ++ private final int h; ++ private final int i; ++ ++ public DataBits(int i, int j) { ++ this(i, j, (long[]) null); ++ } ++ ++ public DataBits(int i, int j, @Nullable long[] along) { ++ Validate.inclusiveBetween(1L, 32L, (long) i); ++ this.e = j; ++ this.c = i; ++ this.d = (1L << i) - 1L; ++ this.f = (char) (64 / i); ++ int k = 3 * (this.f - 1); ++ ++ this.g = DataBits.a[k + 0]; ++ this.h = DataBits.a[k + 1]; ++ this.i = DataBits.a[k + 2]; ++ int l = (j + this.f - 1) / this.f; ++ ++ if (along != null) { ++ if (along.length != l) { ++ throw (RuntimeException) SystemUtils.c((Throwable) (new RuntimeException("Invalid length given for storage, got: " + along.length + " but expected: " + l))); ++ } ++ ++ this.b = along; ++ } else { ++ this.b = new long[l]; ++ } ++ ++ } ++ ++ private int b(int i) { ++ long j = Integer.toUnsignedLong(this.g); ++ long k = Integer.toUnsignedLong(this.h); ++ ++ return (int) ((long) i * j + k >> 32 >> this.i); ++ } ++ ++ public int a(int i, int j) { ++ Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); ++ Validate.inclusiveBetween(0L, this.d, (long) j); ++ int k = this.b(i); ++ long l = this.b[k]; ++ int i1 = (i - k * this.f) * this.c; ++ int j1 = (int) (l >> i1 & this.d); ++ ++ this.b[k] = l & ~(this.d << i1) | ((long) j & this.d) << i1; ++ return j1; ++ } ++ ++ public void b(int i, int j) { ++ Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); ++ Validate.inclusiveBetween(0L, this.d, (long) j); ++ int k = this.b(i); ++ long l = this.b[k]; ++ int i1 = (i - k * this.f) * this.c; ++ ++ this.b[k] = l & ~(this.d << i1) | ((long) j & this.d) << i1; ++ } ++ ++ public int a(int i) { ++ Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); ++ int j = this.b(i); ++ long k = this.b[j]; ++ int l = (i - j * this.f) * this.c; ++ ++ return (int) (k >> l & this.d); ++ } ++ ++ public long[] a() { ++ return this.b; ++ } ++ ++ public int b() { ++ return this.e; ++ } ++ ++ public void a(IntConsumer intconsumer) { ++ int i = 0; ++ long[] along = this.b; ++ int j = along.length; ++ ++ for (int k = 0; k < j; ++k) { ++ long l = along[k]; ++ ++ for (int i1 = 0; i1 < this.f; ++i1) { ++ intconsumer.accept((int) (l & this.d)); ++ l >>= this.c; ++ ++i; ++ if (i >= this.e) { ++ return; ++ } ++ } ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/util/MathHelper.java b/src/main/java/net/minecraft/util/MathHelper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..caa628417bb9c1c65b037e4f3f762b08272c6d09 +--- /dev/null ++++ b/src/main/java/net/minecraft/util/MathHelper.java +@@ -0,0 +1,469 @@ ++package net.minecraft.util; ++ ++import java.util.Random; ++import java.util.UUID; ++import java.util.function.IntPredicate; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BaseBlockPosition; ++ ++public class MathHelper { ++ ++ public static final float a = c(2.0F); ++ private static final float[] b = (float[]) SystemUtils.a((Object) (new float[65536]), (afloat) -> { ++ for (int i = 0; i < afloat.length; ++i) { ++ afloat[i] = (float) Math.sin((double) i * 3.141592653589793D * 2.0D / 65536.0D); ++ } ++ ++ }); ++ private static final Random c = new Random(); ++ private static final int[] d = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; ++ private static final double e = Double.longBitsToDouble(4805340802404319232L); ++ private static final double[] f = new double[257]; ++ private static final double[] g = new double[257]; ++ ++ public static float sin(float f) { ++ return MathHelper.b[(int) (f * 10430.378F) & '\uffff']; ++ } ++ ++ public static float cos(float f) { ++ return MathHelper.b[(int) (f * 10430.378F + 16384.0F) & '\uffff']; ++ } ++ ++ public static float c(float f) { ++ return (float) Math.sqrt((double) f); ++ } ++ ++ public static float sqrt(double d0) { ++ return (float) Math.sqrt(d0); ++ } ++ ++ public static int d(float f) { ++ int i = (int) f; ++ ++ return f < (float) i ? i - 1 : i; ++ } ++ ++ public static int floor(double d0) { ++ int i = (int) d0; ++ ++ return d0 < (double) i ? i - 1 : i; ++ } ++ ++ public static long d(double d0) { ++ long i = (long) d0; ++ ++ return d0 < (double) i ? i - 1L : i; ++ } ++ ++ public static float e(float f) { ++ return Math.abs(f); ++ } ++ ++ public static int a(int i) { ++ return Math.abs(i); ++ } ++ ++ public static int f(float f) { ++ int i = (int) f; ++ ++ return f > (float) i ? i + 1 : i; ++ } ++ ++ public static int f(double d0) { ++ int i = (int) d0; ++ ++ return d0 > (double) i ? i + 1 : i; ++ } ++ ++ public static int clamp(int i, int j, int k) { ++ return i < j ? j : (i > k ? k : i); ++ } ++ ++ public static float a(float f, float f1, float f2) { ++ return f < f1 ? f1 : (f > f2 ? f2 : f); ++ } ++ ++ public static double a(double d0, double d1, double d2) { ++ return d0 < d1 ? d1 : (d0 > d2 ? d2 : d0); ++ } ++ ++ public static double b(double d0, double d1, double d2) { ++ return d2 < 0.0D ? d0 : (d2 > 1.0D ? d1 : d(d2, d0, d1)); ++ } ++ ++ public static double a(double d0, double d1) { ++ if (d0 < 0.0D) { ++ d0 = -d0; ++ } ++ ++ if (d1 < 0.0D) { ++ d1 = -d1; ++ } ++ ++ return d0 > d1 ? d0 : d1; ++ } ++ ++ public static int a(int i, int j) { ++ return Math.floorDiv(i, j); ++ } ++ ++ public static int nextInt(Random random, int i, int j) { ++ return i >= j ? i : random.nextInt(j - i + 1) + i; ++ } ++ ++ public static float a(Random random, float f, float f1) { ++ return f >= f1 ? f : random.nextFloat() * (f1 - f) + f; ++ } ++ ++ public static double a(Random random, double d0, double d1) { ++ return d0 >= d1 ? d0 : random.nextDouble() * (d1 - d0) + d0; ++ } ++ ++ public static double a(long[] along) { ++ long i = 0L; ++ long[] along1 = along; ++ int j = along.length; ++ ++ for (int k = 0; k < j; ++k) { ++ long l = along1[k]; ++ ++ i += l; ++ } ++ ++ return (double) i / (double) along.length; ++ } ++ ++ public static boolean b(double d0, double d1) { ++ return Math.abs(d1 - d0) < 9.999999747378752E-6D; ++ } ++ ++ public static int b(int i, int j) { ++ return Math.floorMod(i, j); ++ } ++ ++ public static float g(float f) { ++ float f1 = f % 360.0F; ++ ++ if (f1 >= 180.0F) { ++ f1 -= 360.0F; ++ } ++ ++ if (f1 < -180.0F) { ++ f1 += 360.0F; ++ } ++ ++ return f1; ++ } ++ ++ public static double g(double d0) { ++ double d1 = d0 % 360.0D; ++ ++ if (d1 >= 180.0D) { ++ d1 -= 360.0D; ++ } ++ ++ if (d1 < -180.0D) { ++ d1 += 360.0D; ++ } ++ ++ return d1; ++ } ++ ++ public static float c(float f, float f1) { ++ return g(f1 - f); ++ } ++ ++ public static float d(float f, float f1) { ++ return e(c(f, f1)); ++ } ++ ++ public static float b(float f, float f1, float f2) { ++ float f3 = c(f, f1); ++ float f4 = a(f3, -f2, f2); ++ ++ return f1 - f4; ++ } ++ ++ public static float c(float f, float f1, float f2) { ++ f2 = e(f2); ++ return f < f1 ? a(f + f2, f, f1) : a(f - f2, f1, f); ++ } ++ ++ public static float d(float f, float f1, float f2) { ++ float f3 = c(f, f1); ++ ++ return c(f, f + f3, f2); ++ } ++ ++ public static int c(int i) { ++ int j = i - 1; ++ ++ j |= j >> 1; ++ j |= j >> 2; ++ j |= j >> 4; ++ j |= j >> 8; ++ j |= j >> 16; ++ return j + 1; ++ } ++ ++ public static boolean d(int i) { ++ return i != 0 && (i & i - 1) == 0; ++ } ++ ++ public static int e(int i) { ++ i = d(i) ? i : c(i); ++ return MathHelper.d[(int) ((long) i * 125613361L >> 27) & 31]; ++ } ++ ++ public static int f(int i) { ++ return e(i) - (d(i) ? 0 : 1); ++ } ++ ++ public static int c(int i, int j) { ++ if (j == 0) { ++ return 0; ++ } else if (i == 0) { ++ return j; ++ } else { ++ if (i < 0) { ++ j *= -1; ++ } ++ ++ int k = i % j; ++ ++ return k == 0 ? i : i + j - k; ++ } ++ } ++ ++ public static float h(float f) { ++ return f - (float) d(f); ++ } ++ ++ public static double h(double d0) { ++ return d0 - (double) d(d0); ++ } ++ ++ public static long a(BaseBlockPosition baseblockposition) { ++ return c(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); ++ } ++ ++ public static long c(int i, int j, int k) { ++ long l = (long) (i * 3129871) ^ (long) k * 116129781L ^ (long) j; ++ ++ l = l * l * 42317861L + l * 11L; ++ return l >> 16; ++ } ++ ++ public static UUID a(Random random) { ++ long i = random.nextLong() & -61441L | 16384L; ++ long j = random.nextLong() & 4611686018427387903L | Long.MIN_VALUE; ++ ++ return new UUID(i, j); ++ } ++ ++ public static UUID a() { ++ return a(MathHelper.c); ++ } ++ ++ public static double c(double d0, double d1, double d2) { ++ return (d0 - d1) / (d2 - d1); ++ } ++ ++ public static double d(double d0, double d1) { ++ double d2 = d1 * d1 + d0 * d0; ++ ++ if (Double.isNaN(d2)) { ++ return Double.NaN; ++ } else { ++ boolean flag = d0 < 0.0D; ++ ++ if (flag) { ++ d0 = -d0; ++ } ++ ++ boolean flag1 = d1 < 0.0D; ++ ++ if (flag1) { ++ d1 = -d1; ++ } ++ ++ boolean flag2 = d0 > d1; ++ double d3; ++ ++ if (flag2) { ++ d3 = d1; ++ d1 = d0; ++ d0 = d3; ++ } ++ ++ d3 = i(d2); ++ d1 *= d3; ++ d0 *= d3; ++ double d4 = MathHelper.e + d0; ++ int i = (int) Double.doubleToRawLongBits(d4); ++ double d5 = MathHelper.f[i]; ++ double d6 = MathHelper.g[i]; ++ double d7 = d4 - MathHelper.e; ++ double d8 = d0 * d6 - d1 * d7; ++ double d9 = (6.0D + d8 * d8) * d8 * 0.16666666666666666D; ++ double d10 = d5 + d9; ++ ++ if (flag2) { ++ d10 = 1.5707963267948966D - d10; ++ } ++ ++ if (flag1) { ++ d10 = 3.141592653589793D - d10; ++ } ++ ++ if (flag) { ++ d10 = -d10; ++ } ++ ++ return d10; ++ } ++ } ++ ++ public static double i(double d0) { ++ double d1 = 0.5D * d0; ++ long i = Double.doubleToRawLongBits(d0); ++ ++ i = 6910469410427058090L - (i >> 1); ++ d0 = Double.longBitsToDouble(i); ++ d0 *= 1.5D - d1 * d0 * d0; ++ return d0; ++ } ++ ++ public static int f(float f, float f1, float f2) { ++ int i = (int) (f * 6.0F) % 6; ++ float f3 = f * 6.0F - (float) i; ++ float f4 = f2 * (1.0F - f1); ++ float f5 = f2 * (1.0F - f3 * f1); ++ float f6 = f2 * (1.0F - (1.0F - f3) * f1); ++ float f7; ++ float f8; ++ float f9; ++ ++ switch (i) { ++ case 0: ++ f7 = f2; ++ f8 = f6; ++ f9 = f4; ++ break; ++ case 1: ++ f7 = f5; ++ f8 = f2; ++ f9 = f4; ++ break; ++ case 2: ++ f7 = f4; ++ f8 = f2; ++ f9 = f6; ++ break; ++ case 3: ++ f7 = f4; ++ f8 = f5; ++ f9 = f2; ++ break; ++ case 4: ++ f7 = f6; ++ f8 = f4; ++ f9 = f2; ++ break; ++ case 5: ++ f7 = f2; ++ f8 = f4; ++ f9 = f5; ++ break; ++ default: ++ throw new RuntimeException("Something went wrong when converting from HSV to RGB. Input was " + f + ", " + f1 + ", " + f2); ++ } ++ ++ int j = clamp((int) (f7 * 255.0F), 0, 255); ++ int k = clamp((int) (f8 * 255.0F), 0, 255); ++ int l = clamp((int) (f9 * 255.0F), 0, 255); ++ ++ return j << 16 | k << 8 | l; ++ } ++ ++ public static int g(int i) { ++ i ^= i >>> 16; ++ i *= -2048144789; ++ i ^= i >>> 13; ++ i *= -1028477387; ++ i ^= i >>> 16; ++ return i; ++ } ++ ++ public static int a(int i, int j, IntPredicate intpredicate) { ++ int k = j - i; ++ ++ while (k > 0) { ++ int l = k / 2; ++ int i1 = i + l; ++ ++ if (intpredicate.test(i1)) { ++ k = l; ++ } else { ++ i = i1 + 1; ++ k -= l + 1; ++ } ++ } ++ ++ return i; ++ } ++ ++ public static float g(float f, float f1, float f2) { ++ return f1 + f * (f2 - f1); ++ } ++ ++ public static double d(double d0, double d1, double d2) { ++ return d1 + d0 * (d2 - d1); ++ } ++ ++ public static double a(double d0, double d1, double d2, double d3, double d4, double d5) { ++ return d(d1, d(d0, d2, d3), d(d0, d4, d5)); ++ } ++ ++ public static double a(double d0, double d1, double d2, double d3, double d4, double d5, double d6, double d7, double d8, double d9, double d10) { ++ return d(d2, a(d0, d1, d3, d4, d5, d6), a(d0, d1, d7, d8, d9, d10)); ++ } ++ ++ public static double j(double d0) { ++ return d0 * d0 * d0 * (d0 * (d0 * 6.0D - 15.0D) + 10.0D); ++ } ++ ++ public static int k(double d0) { ++ return d0 == 0.0D ? 0 : (d0 > 0.0D ? 1 : -1); ++ } ++ ++ @Deprecated ++ public static float j(float f, float f1, float f2) { ++ float f3; ++ ++ for (f3 = f1 - f; f3 < -180.0F; f3 += 360.0F) { ++ ; ++ } ++ ++ while (f3 >= 180.0F) { ++ f3 -= 360.0F; ++ } ++ ++ return f + f2 * f3; ++ } ++ ++ public static float k(float f) { ++ return f * f; ++ } ++ ++ static { ++ for (int i = 0; i < 257; ++i) { ++ double d0 = (double) i / 256.0D; ++ double d1 = Math.asin(d0); ++ ++ MathHelper.g[i] = Math.cos(d1); ++ MathHelper.f[i] = d1; ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/util/RegistryID.java b/src/main/java/net/minecraft/util/RegistryID.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d88e55c64f80707b4a9b1e271714c2dbdee9a38a +--- /dev/null ++++ b/src/main/java/net/minecraft/util/RegistryID.java +@@ -0,0 +1,158 @@ ++package net.minecraft.util; ++ ++import com.google.common.base.Predicates; ++import com.google.common.collect.Iterators; ++import java.util.Arrays; ++import java.util.Iterator; ++import javax.annotation.Nullable; ++import net.minecraft.core.Registry; ++ ++public class RegistryID implements Registry { ++ ++ private static final Object a = null; ++ private K[] b; ++ private int[] c; ++ private K[] d; ++ private int e; ++ private int f; ++ ++ public RegistryID(int i) { ++ i = (int) ((float) i / 0.8F); ++ this.b = (Object[]) (new Object[i]); ++ this.c = new int[i]; ++ this.d = (Object[]) (new Object[i]); ++ } ++ ++ public int getId(@Nullable K k0) { ++ return this.c(this.b(k0, this.d(k0))); ++ } ++ ++ @Nullable ++ @Override ++ public K fromId(int i) { ++ return i >= 0 && i < this.d.length ? this.d[i] : null; ++ } ++ ++ private int c(int i) { ++ return i == -1 ? -1 : this.c[i]; ++ } ++ ++ public int c(K k0) { ++ int i = this.c(); ++ ++ this.a(k0, i); ++ return i; ++ } ++ ++ private int c() { ++ while (this.e < this.d.length && this.d[this.e] != null) { ++ ++this.e; ++ } ++ ++ return this.e; ++ } ++ ++ private void d(int i) { ++ K[] ak = this.b; ++ int[] aint = this.c; ++ ++ this.b = (Object[]) (new Object[i]); ++ this.c = new int[i]; ++ this.d = (Object[]) (new Object[i]); ++ this.e = 0; ++ this.f = 0; ++ ++ for (int j = 0; j < ak.length; ++j) { ++ if (ak[j] != null) { ++ this.a(ak[j], aint[j]); ++ } ++ } ++ ++ } ++ ++ public void a(K k0, int i) { ++ int j = Math.max(i, this.f + 1); ++ int k; ++ ++ if ((float) j >= (float) this.b.length * 0.8F) { ++ for (k = this.b.length << 1; k < i; k <<= 1) { ++ ; ++ } ++ ++ this.d(k); ++ } ++ ++ k = this.e(this.d(k0)); ++ this.b[k] = k0; ++ this.c[k] = i; ++ this.d[i] = k0; ++ ++this.f; ++ if (i == this.e) { ++ ++this.e; ++ } ++ ++ } ++ ++ private int d(@Nullable K k0) { ++ return (MathHelper.g(System.identityHashCode(k0)) & Integer.MAX_VALUE) % this.b.length; ++ } ++ ++ private int b(@Nullable K k0, int i) { ++ int j; ++ ++ for (j = i; j < this.b.length; ++j) { ++ if (this.b[j] == k0) { ++ return j; ++ } ++ ++ if (this.b[j] == RegistryID.a) { ++ return -1; ++ } ++ } ++ ++ for (j = 0; j < i; ++j) { ++ if (this.b[j] == k0) { ++ return j; ++ } ++ ++ if (this.b[j] == RegistryID.a) { ++ return -1; ++ } ++ } ++ ++ return -1; ++ } ++ ++ private int e(int i) { ++ int j; ++ ++ for (j = i; j < this.b.length; ++j) { ++ if (this.b[j] == RegistryID.a) { ++ return j; ++ } ++ } ++ ++ for (j = 0; j < i; ++j) { ++ if (this.b[j] == RegistryID.a) { ++ return j; ++ } ++ } ++ ++ throw new RuntimeException("Overflowed :("); ++ } ++ ++ public Iterator iterator() { ++ return Iterators.filter(Iterators.forArray(this.d), Predicates.notNull()); ++ } ++ ++ public void a() { ++ Arrays.fill(this.b, (Object) null); ++ Arrays.fill(this.d, (Object) null); ++ this.e = 0; ++ this.f = 0; ++ } ++ ++ public int b() { ++ return this.f; ++ } ++} +diff --git a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6abf1459cc97c261daf3c116521574d31a77a338 +--- /dev/null ++++ b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +@@ -0,0 +1,133 @@ ++package net.minecraft.util.thread; ++ ++import com.google.common.collect.Queues; ++import java.util.Queue; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.Executor; ++import java.util.concurrent.locks.LockSupport; ++import java.util.function.BooleanSupplier; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public abstract class IAsyncTaskHandler implements Mailbox, Executor { ++ ++ private final String b; ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final Queue d = Queues.newConcurrentLinkedQueue(); ++ private int e; ++ ++ protected IAsyncTaskHandler(String s) { ++ this.b = s; ++ } ++ ++ protected abstract R postToMainThread(Runnable runnable); ++ ++ protected abstract boolean canExecute(R r0); ++ ++ public boolean isMainThread() { ++ return Thread.currentThread() == this.getThread(); ++ } ++ ++ protected abstract Thread getThread(); ++ ++ protected boolean isNotMainThread() { ++ return !this.isMainThread(); ++ } ++ ++ public int bi() { ++ return this.d.size(); ++ } ++ ++ @Override ++ public String bj() { ++ return this.b; ++ } ++ ++ private CompletableFuture executeFuture(Runnable runnable) { ++ return CompletableFuture.supplyAsync(() -> { ++ runnable.run(); ++ return null; ++ }, this); ++ } ++ ++ public CompletableFuture f(Runnable runnable) { ++ if (this.isNotMainThread()) { ++ return this.executeFuture(runnable); ++ } else { ++ runnable.run(); ++ return CompletableFuture.completedFuture((Object) null); ++ } ++ } ++ ++ public void executeSync(Runnable runnable) { ++ if (!this.isMainThread()) { ++ this.executeFuture(runnable).join(); ++ } else { ++ runnable.run(); ++ } ++ ++ } ++ ++ public void a(R r0) { ++ this.d.add(r0); ++ LockSupport.unpark(this.getThread()); ++ } ++ ++ public void execute(Runnable runnable) { ++ if (this.isNotMainThread()) { ++ this.a(this.postToMainThread(runnable)); ++ } else { ++ runnable.run(); ++ } ++ ++ } ++ ++ protected void executeAll() { ++ while (this.executeNext()) { ++ ; ++ } ++ ++ } ++ ++ protected boolean executeNext() { ++ R r0 = (Runnable) this.d.peek(); ++ ++ if (r0 == null) { ++ return false; ++ } else if (this.e == 0 && !this.canExecute(r0)) { ++ return false; ++ } else { ++ this.executeTask((Runnable) this.d.remove()); ++ return true; ++ } ++ } ++ ++ public void awaitTasks(BooleanSupplier booleansupplier) { ++ ++this.e; ++ ++ try { ++ while (!booleansupplier.getAsBoolean()) { ++ if (!this.executeNext()) { ++ this.bm(); ++ } ++ } ++ } finally { ++ --this.e; ++ } ++ ++ } ++ ++ protected void bm() { ++ Thread.yield(); ++ LockSupport.parkNanos("waiting for tasks", 100000L); ++ } ++ ++ protected void executeTask(R r0) { ++ try { ++ r0.run(); ++ } catch (Exception exception) { ++ IAsyncTaskHandler.LOGGER.fatal("Error executing task on {}", this.bj(), exception); ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/util/thread/PairedQueue.java b/src/main/java/net/minecraft/util/thread/PairedQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..024b414aae32c8ad32bdf031361257fc74b80eb3 +--- /dev/null ++++ b/src/main/java/net/minecraft/util/thread/PairedQueue.java +@@ -0,0 +1,107 @@ ++package net.minecraft.util.thread; ++ ++import com.google.common.collect.Queues; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Queue; ++import java.util.stream.Collectors; ++import java.util.stream.IntStream; ++import javax.annotation.Nullable; ++ ++public interface PairedQueue { ++ ++ @Nullable ++ F a(); ++ ++ boolean a(T t0); ++ ++ boolean b(); ++ ++ public static final class a implements PairedQueue { ++ ++ private final List> a; ++ ++ public a(int i) { ++ this.a = (List) IntStream.range(0, i).mapToObj((j) -> { ++ return Queues.newConcurrentLinkedQueue(); ++ }).collect(Collectors.toList()); ++ } ++ ++ @Nullable ++ @Override ++ public Runnable a() { ++ Iterator iterator = this.a.iterator(); ++ ++ Runnable runnable; ++ ++ do { ++ if (!iterator.hasNext()) { ++ return null; ++ } ++ ++ Queue queue = (Queue) iterator.next(); ++ ++ runnable = (Runnable) queue.poll(); ++ } while (runnable == null); ++ ++ return runnable; ++ } ++ ++ public boolean a(PairedQueue.b pairedqueue_b) { ++ int i = pairedqueue_b.a(); ++ ++ ((Queue) this.a.get(i)).add(pairedqueue_b); ++ return true; ++ } ++ ++ @Override ++ public boolean b() { ++ return this.a.stream().allMatch(Collection::isEmpty); ++ } ++ } ++ ++ public static final class b implements Runnable { ++ ++ private final int a; ++ private final Runnable b; ++ ++ public b(int i, Runnable runnable) { ++ this.a = i; ++ this.b = runnable; ++ } ++ ++ public void run() { ++ this.b.run(); ++ } ++ ++ public int a() { ++ return this.a; ++ } ++ } ++ ++ public static final class c implements PairedQueue { ++ ++ private final Queue a; ++ ++ public c(Queue queue) { ++ this.a = queue; ++ } ++ ++ @Nullable ++ @Override ++ public T a() { ++ return this.a.poll(); ++ } ++ ++ @Override ++ public boolean a(T t0) { ++ return this.a.add(t0); ++ } ++ ++ @Override ++ public boolean b() { ++ return this.a.isEmpty(); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/util/thread/ThreadedMailbox.java b/src/main/java/net/minecraft/util/thread/ThreadedMailbox.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e872e5c9157ca499491042040f0cc0f14f7d2946 +--- /dev/null ++++ b/src/main/java/net/minecraft/util/thread/ThreadedMailbox.java +@@ -0,0 +1,152 @@ ++package net.minecraft.util.thread; ++ ++import it.unimi.dsi.fastutil.ints.Int2BooleanFunction; ++import java.util.concurrent.ConcurrentLinkedQueue; ++import java.util.concurrent.Executor; ++import java.util.concurrent.RejectedExecutionException; ++import java.util.concurrent.atomic.AtomicInteger; ++import net.minecraft.SharedConstants; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class ThreadedMailbox implements Mailbox, AutoCloseable, Runnable { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final AtomicInteger c = new AtomicInteger(0); ++ public final PairedQueue a; ++ private final Executor d; ++ private final String e; ++ ++ public static ThreadedMailbox a(Executor executor, String s) { ++ return new ThreadedMailbox<>(new PairedQueue.c<>(new ConcurrentLinkedQueue()), executor, s); ++ } ++ ++ public ThreadedMailbox(PairedQueue pairedqueue, Executor executor, String s) { ++ this.d = executor; ++ this.a = pairedqueue; ++ this.e = s; ++ } ++ ++ private boolean a() { ++ int i; ++ ++ do { ++ i = this.c.get(); ++ if ((i & 3) != 0) { ++ return false; ++ } ++ } while (!this.c.compareAndSet(i, i | 2)); ++ ++ return true; ++ } ++ ++ private void b() { ++ int i; ++ ++ do { ++ i = this.c.get(); ++ } while (!this.c.compareAndSet(i, i & -3)); ++ ++ } ++ ++ private boolean c() { ++ return (this.c.get() & 1) != 0 ? false : !this.a.b(); ++ } ++ ++ @Override ++ public void close() { ++ int i; ++ ++ do { ++ i = this.c.get(); ++ } while (!this.c.compareAndSet(i, i | 1)); ++ ++ } ++ ++ private boolean d() { ++ return (this.c.get() & 2) != 0; ++ } ++ ++ private boolean e() { ++ if (!this.d()) { ++ return false; ++ } else { ++ Runnable runnable = (Runnable) this.a.a(); ++ ++ if (runnable == null) { ++ return false; ++ } else { ++ Thread thread; ++ String s; ++ ++ if (SharedConstants.d) { ++ thread = Thread.currentThread(); ++ s = thread.getName(); ++ thread.setName(this.e); ++ } else { ++ thread = null; ++ s = null; ++ } ++ ++ runnable.run(); ++ if (thread != null) { ++ thread.setName(s); ++ } ++ ++ return true; ++ } ++ } ++ } ++ ++ public void run() { ++ try { ++ this.a((i) -> { ++ return i == 0; ++ }); ++ } finally { ++ this.b(); ++ this.f(); ++ } ++ ++ } ++ ++ @Override ++ public void a(T t0) { ++ this.a.a(t0); ++ this.f(); ++ } ++ ++ private void f() { ++ if (this.c() && this.a()) { ++ try { ++ this.d.execute(this); ++ } catch (RejectedExecutionException rejectedexecutionexception) { ++ try { ++ this.d.execute(this); ++ } catch (RejectedExecutionException rejectedexecutionexception1) { ++ ThreadedMailbox.LOGGER.error("Cound not schedule mailbox", rejectedexecutionexception1); ++ } ++ } ++ } ++ ++ } ++ ++ private int a(Int2BooleanFunction int2booleanfunction) { ++ int i; ++ ++ for (i = 0; int2booleanfunction.get(i) && this.e(); ++i) { ++ ; ++ } ++ ++ return i; ++ } ++ ++ public String toString() { ++ return this.e + " " + this.c.get() + " " + this.a.b(); ++ } ++ ++ @Override ++ public String bj() { ++ return this.e; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/BossBattle.java b/src/main/java/net/minecraft/world/BossBattle.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e3c21e73f54c7312dd2e260079d727224fc08256 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/BossBattle.java +@@ -0,0 +1,154 @@ ++package net.minecraft.world; ++ ++import java.util.UUID; ++import net.minecraft.EnumChatFormat; ++import net.minecraft.network.chat.IChatBaseComponent; ++ ++public abstract class BossBattle { ++ ++ private final UUID h; ++ public IChatBaseComponent title; ++ protected float b; ++ public BossBattle.BarColor color; ++ public BossBattle.BarStyle style; ++ protected boolean e; ++ protected boolean f; ++ protected boolean g; ++ ++ public BossBattle(UUID uuid, IChatBaseComponent ichatbasecomponent, BossBattle.BarColor bossbattle_barcolor, BossBattle.BarStyle bossbattle_barstyle) { ++ this.h = uuid; ++ this.title = ichatbasecomponent; ++ this.color = bossbattle_barcolor; ++ this.style = bossbattle_barstyle; ++ this.b = 1.0F; ++ } ++ ++ public UUID i() { ++ return this.h; ++ } ++ ++ public IChatBaseComponent j() { ++ return this.title; ++ } ++ ++ public void a(IChatBaseComponent ichatbasecomponent) { ++ this.title = ichatbasecomponent; ++ } ++ ++ public float getProgress() { ++ return this.b; ++ } ++ ++ public void setProgress(float f) { ++ this.b = f; ++ } ++ ++ public BossBattle.BarColor l() { ++ return this.color; ++ } ++ ++ public void a(BossBattle.BarColor bossbattle_barcolor) { ++ this.color = bossbattle_barcolor; ++ } ++ ++ public BossBattle.BarStyle m() { ++ return this.style; ++ } ++ ++ public void a(BossBattle.BarStyle bossbattle_barstyle) { ++ this.style = bossbattle_barstyle; ++ } ++ ++ public boolean isDarkenSky() { ++ return this.e; ++ } ++ ++ public BossBattle setDarkenSky(boolean flag) { ++ this.e = flag; ++ return this; ++ } ++ ++ public boolean isPlayMusic() { ++ return this.f; ++ } ++ ++ public BossBattle setPlayMusic(boolean flag) { ++ this.f = flag; ++ return this; ++ } ++ ++ public BossBattle setCreateFog(boolean flag) { ++ this.g = flag; ++ return this; ++ } ++ ++ public boolean isCreateFog() { ++ return this.g; ++ } ++ ++ public static enum BarStyle { ++ ++ PROGRESS("progress"), NOTCHED_6("notched_6"), NOTCHED_10("notched_10"), NOTCHED_12("notched_12"), NOTCHED_20("notched_20"); ++ ++ private final String f; ++ ++ private BarStyle(String s) { ++ this.f = s; ++ } ++ ++ public String a() { ++ return this.f; ++ } ++ ++ public static BossBattle.BarStyle a(String s) { ++ BossBattle.BarStyle[] abossbattle_barstyle = values(); ++ int i = abossbattle_barstyle.length; ++ ++ for (int j = 0; j < i; ++j) { ++ BossBattle.BarStyle bossbattle_barstyle = abossbattle_barstyle[j]; ++ ++ if (bossbattle_barstyle.f.equals(s)) { ++ return bossbattle_barstyle; ++ } ++ } ++ ++ return BossBattle.BarStyle.PROGRESS; ++ } ++ } ++ ++ public static enum BarColor { ++ ++ PINK("pink", EnumChatFormat.RED), BLUE("blue", EnumChatFormat.BLUE), RED("red", EnumChatFormat.DARK_RED), GREEN("green", EnumChatFormat.GREEN), YELLOW("yellow", EnumChatFormat.YELLOW), PURPLE("purple", EnumChatFormat.DARK_BLUE), WHITE("white", EnumChatFormat.WHITE); ++ ++ private final String h; ++ private final EnumChatFormat i; ++ ++ private BarColor(String s, EnumChatFormat enumchatformat) { ++ this.h = s; ++ this.i = enumchatformat; ++ } ++ ++ public EnumChatFormat a() { ++ return this.i; ++ } ++ ++ public String b() { ++ return this.h; ++ } ++ ++ public static BossBattle.BarColor a(String s) { ++ BossBattle.BarColor[] abossbattle_barcolor = values(); ++ int i = abossbattle_barcolor.length; ++ ++ for (int j = 0; j < i; ++j) { ++ BossBattle.BarColor bossbattle_barcolor = abossbattle_barcolor[j]; ++ ++ if (bossbattle_barcolor.h.equals(s)) { ++ return bossbattle_barcolor; ++ } ++ } ++ ++ return BossBattle.BarColor.WHITE; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/InteractionResultWrapper.java b/src/main/java/net/minecraft/world/InteractionResultWrapper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dd17c111670e637b574f5c7f38d27848900ce194 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/InteractionResultWrapper.java +@@ -0,0 +1,40 @@ ++package net.minecraft.world; ++ ++public class InteractionResultWrapper { ++ ++ private final EnumInteractionResult a; ++ private final T b; ++ ++ public InteractionResultWrapper(EnumInteractionResult enuminteractionresult, T t0) { ++ this.a = enuminteractionresult; ++ this.b = t0; ++ } ++ ++ public EnumInteractionResult a() { ++ return this.a; ++ } ++ ++ public T b() { ++ return this.b; ++ } ++ ++ public static InteractionResultWrapper success(T t0) { ++ return new InteractionResultWrapper<>(EnumInteractionResult.SUCCESS, t0); ++ } ++ ++ public static InteractionResultWrapper consume(T t0) { ++ return new InteractionResultWrapper<>(EnumInteractionResult.CONSUME, t0); ++ } ++ ++ public static InteractionResultWrapper pass(T t0) { ++ return new InteractionResultWrapper<>(EnumInteractionResult.PASS, t0); ++ } ++ ++ public static InteractionResultWrapper fail(T t0) { ++ return new InteractionResultWrapper<>(EnumInteractionResult.FAIL, t0); ++ } ++ ++ public static InteractionResultWrapper a(T t0, boolean flag) { ++ return flag ? success(t0) : consume(t0); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f6f79ed9c38206cc6a4feb5504e854a476868aec +--- /dev/null ++++ b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java +@@ -0,0 +1,227 @@ ++package net.minecraft.world.damagesource; ++ ++import com.google.common.collect.Lists; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Optional; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.network.chat.ChatMessage; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsBlock; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++ ++public class CombatTracker { ++ ++ private final List a = Lists.newArrayList(); ++ private final EntityLiving b; ++ private int c; ++ private int d; ++ private int e; ++ private boolean f; ++ private boolean g; ++ private String h; ++ ++ public CombatTracker(EntityLiving entityliving) { ++ this.b = entityliving; ++ } ++ ++ public void a() { ++ this.k(); ++ Optional optional = this.b.dr(); ++ ++ if (optional.isPresent()) { ++ IBlockData iblockdata = this.b.world.getType((BlockPosition) optional.get()); ++ ++ if (!iblockdata.a(Blocks.LADDER) && !iblockdata.a((Tag) TagsBlock.TRAPDOORS)) { ++ if (iblockdata.a(Blocks.VINE)) { ++ this.h = "vines"; ++ } else if (!iblockdata.a(Blocks.WEEPING_VINES) && !iblockdata.a(Blocks.WEEPING_VINES_PLANT)) { ++ if (!iblockdata.a(Blocks.TWISTING_VINES) && !iblockdata.a(Blocks.TWISTING_VINES_PLANT)) { ++ if (iblockdata.a(Blocks.SCAFFOLDING)) { ++ this.h = "scaffolding"; ++ } else { ++ this.h = "other_climbable"; ++ } ++ } else { ++ this.h = "twisting_vines"; ++ } ++ } else { ++ this.h = "weeping_vines"; ++ } ++ } else { ++ this.h = "ladder"; ++ } ++ } else if (this.b.isInWater()) { ++ this.h = "water"; ++ } ++ ++ } ++ ++ public void trackDamage(DamageSource damagesource, float f, float f1) { ++ this.g(); ++ this.a(); ++ CombatEntry combatentry = new CombatEntry(damagesource, this.b.ticksLived, f, f1, this.h, this.b.fallDistance); ++ ++ this.a.add(combatentry); ++ this.c = this.b.ticksLived; ++ this.g = true; ++ if (combatentry.f() && !this.f && this.b.isAlive()) { ++ this.f = true; ++ this.d = this.b.ticksLived; ++ this.e = this.d; ++ this.b.enterCombat(); ++ } ++ ++ } ++ ++ public IChatBaseComponent getDeathMessage() { ++ if (this.a.isEmpty()) { ++ return new ChatMessage("death.attack.generic", new Object[]{this.b.getScoreboardDisplayName()}); ++ } else { ++ CombatEntry combatentry = this.j(); ++ CombatEntry combatentry1 = (CombatEntry) this.a.get(this.a.size() - 1); ++ IChatBaseComponent ichatbasecomponent = combatentry1.h(); ++ Entity entity = combatentry1.a().getEntity(); ++ Object object; ++ ++ if (combatentry != null && combatentry1.a() == DamageSource.FALL) { ++ IChatBaseComponent ichatbasecomponent1 = combatentry.h(); ++ ++ if (combatentry.a() != DamageSource.FALL && combatentry.a() != DamageSource.OUT_OF_WORLD) { ++ if (ichatbasecomponent1 != null && (ichatbasecomponent == null || !ichatbasecomponent1.equals(ichatbasecomponent))) { ++ Entity entity1 = combatentry.a().getEntity(); ++ ItemStack itemstack = entity1 instanceof EntityLiving ? ((EntityLiving) entity1).getItemInMainHand() : ItemStack.b; ++ ++ if (!itemstack.isEmpty() && itemstack.hasName()) { ++ object = new ChatMessage("death.fell.assist.item", new Object[]{this.b.getScoreboardDisplayName(), ichatbasecomponent1, itemstack.C()}); ++ } else { ++ object = new ChatMessage("death.fell.assist", new Object[]{this.b.getScoreboardDisplayName(), ichatbasecomponent1}); ++ } ++ } else if (ichatbasecomponent != null) { ++ ItemStack itemstack1 = entity instanceof EntityLiving ? ((EntityLiving) entity).getItemInMainHand() : ItemStack.b; ++ ++ if (!itemstack1.isEmpty() && itemstack1.hasName()) { ++ object = new ChatMessage("death.fell.finish.item", new Object[]{this.b.getScoreboardDisplayName(), ichatbasecomponent, itemstack1.C()}); ++ } else { ++ object = new ChatMessage("death.fell.finish", new Object[]{this.b.getScoreboardDisplayName(), ichatbasecomponent}); ++ } ++ } else { ++ object = new ChatMessage("death.fell.killer", new Object[]{this.b.getScoreboardDisplayName()}); ++ } ++ } else { ++ object = new ChatMessage("death.fell.accident." + this.a(combatentry), new Object[]{this.b.getScoreboardDisplayName()}); ++ } ++ } else { ++ object = combatentry1.a().getLocalizedDeathMessage(this.b); ++ } ++ ++ return (IChatBaseComponent) object; ++ } ++ } ++ ++ @Nullable ++ public EntityLiving c() { ++ EntityLiving entityliving = null; ++ EntityHuman entityhuman = null; ++ float f = 0.0F; ++ float f1 = 0.0F; ++ Iterator iterator = this.a.iterator(); ++ ++ while (iterator.hasNext()) { ++ CombatEntry combatentry = (CombatEntry) iterator.next(); ++ ++ if (combatentry.a().getEntity() instanceof EntityHuman && (entityhuman == null || combatentry.c() > f1)) { ++ f1 = combatentry.c(); ++ entityhuman = (EntityHuman) combatentry.a().getEntity(); ++ } ++ ++ if (combatentry.a().getEntity() instanceof EntityLiving && (entityliving == null || combatentry.c() > f)) { ++ f = combatentry.c(); ++ entityliving = (EntityLiving) combatentry.a().getEntity(); ++ } ++ } ++ ++ if (entityhuman != null && f1 >= f / 3.0F) { ++ return entityhuman; ++ } else { ++ return entityliving; ++ } ++ } ++ ++ @Nullable ++ private CombatEntry j() { ++ CombatEntry combatentry = null; ++ CombatEntry combatentry1 = null; ++ float f = 0.0F; ++ float f1 = 0.0F; ++ ++ for (int i = 0; i < this.a.size(); ++i) { ++ CombatEntry combatentry2 = (CombatEntry) this.a.get(i); ++ CombatEntry combatentry3 = i > 0 ? (CombatEntry) this.a.get(i - 1) : null; ++ ++ if ((combatentry2.a() == DamageSource.FALL || combatentry2.a() == DamageSource.OUT_OF_WORLD) && combatentry2.j() > 0.0F && (combatentry == null || combatentry2.j() > f1)) { ++ if (i > 0) { ++ combatentry = combatentry3; ++ } else { ++ combatentry = combatentry2; ++ } ++ ++ f1 = combatentry2.j(); ++ } ++ ++ if (combatentry2.g() != null && (combatentry1 == null || combatentry2.c() > f)) { ++ combatentry1 = combatentry2; ++ f = combatentry2.c(); ++ } ++ } ++ ++ if (f1 > 5.0F && combatentry != null) { ++ return combatentry; ++ } else if (f > 5.0F && combatentry1 != null) { ++ return combatentry1; ++ } else { ++ return null; ++ } ++ } ++ ++ private String a(CombatEntry combatentry) { ++ return combatentry.g() == null ? "generic" : combatentry.g(); ++ } ++ ++ public int f() { ++ return this.f ? this.b.ticksLived - this.d : this.e - this.d; ++ } ++ ++ private void k() { ++ this.h = null; ++ } ++ ++ public void g() { ++ int i = this.f ? 300 : 100; ++ ++ if (this.g && (!this.b.isAlive() || this.b.ticksLived - this.c > i)) { ++ boolean flag = this.f; ++ ++ this.g = false; ++ this.f = false; ++ this.e = this.b.ticksLived; ++ if (flag) { ++ this.b.exitCombat(); ++ } ++ ++ this.a.clear(); ++ } ++ ++ } ++ ++ public EntityLiving h() { ++ return this.b; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/EnumItemSlot.java b/src/main/java/net/minecraft/world/entity/EnumItemSlot.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8e7673c6072c3f8ddcebd7a719304ea41d809a36 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/EnumItemSlot.java +@@ -0,0 +1,71 @@ ++package net.minecraft.world.entity; ++ ++public enum EnumItemSlot { ++ ++ MAINHAND(EnumItemSlot.Function.HAND, 0, 0, "mainhand"), OFFHAND(EnumItemSlot.Function.HAND, 1, 5, "offhand"), FEET(EnumItemSlot.Function.ARMOR, 0, 1, "feet"), LEGS(EnumItemSlot.Function.ARMOR, 1, 2, "legs"), CHEST(EnumItemSlot.Function.ARMOR, 2, 3, "chest"), HEAD(EnumItemSlot.Function.ARMOR, 3, 4, "head"); ++ ++ private final EnumItemSlot.Function g; ++ private final int h; ++ private final int i; ++ private final String j; ++ ++ private EnumItemSlot(EnumItemSlot.Function enumitemslot_function, int i, int j, String s) { ++ this.g = enumitemslot_function; ++ this.h = i; ++ this.i = j; ++ this.j = s; ++ } ++ ++ public EnumItemSlot.Function a() { ++ return this.g; ++ } ++ ++ public int b() { ++ return this.h; ++ } ++ ++ public int getSlotFlag() { ++ return this.i; ++ } ++ ++ public String getSlotName() { ++ return this.j; ++ } ++ ++ public static EnumItemSlot fromName(String s) { ++ EnumItemSlot[] aenumitemslot = values(); ++ int i = aenumitemslot.length; ++ ++ for (int j = 0; j < i; ++j) { ++ EnumItemSlot enumitemslot = aenumitemslot[j]; ++ ++ if (enumitemslot.getSlotName().equals(s)) { ++ return enumitemslot; ++ } ++ } ++ ++ throw new IllegalArgumentException("Invalid slot '" + s + "'"); ++ } ++ ++ public static EnumItemSlot a(EnumItemSlot.Function enumitemslot_function, int i) { ++ EnumItemSlot[] aenumitemslot = values(); ++ int j = aenumitemslot.length; ++ ++ for (int k = 0; k < j; ++k) { ++ EnumItemSlot enumitemslot = aenumitemslot[k]; ++ ++ if (enumitemslot.a() == enumitemslot_function && enumitemslot.b() == i) { ++ return enumitemslot; ++ } ++ } ++ ++ throw new IllegalArgumentException("Invalid slot '" + enumitemslot_function + "': " + i); ++ } ++ ++ public static enum Function { ++ ++ HAND, ARMOR; ++ ++ private Function() {} ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMapBase.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMapBase.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f6b78c68da555f96033df567da581af52195e6c +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMapBase.java +@@ -0,0 +1,138 @@ ++package net.minecraft.world.entity.ai.attributes; ++ ++import com.google.common.collect.Maps; ++import com.google.common.collect.Multimap; ++import com.google.common.collect.Sets; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Set; ++import java.util.UUID; ++import java.util.stream.Collectors; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.IRegistry; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.resources.MinecraftKey; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class AttributeMapBase { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final Map b = Maps.newHashMap(); ++ private final Set c = Sets.newHashSet(); ++ private final AttributeProvider d; ++ ++ public AttributeMapBase(AttributeProvider attributeprovider) { ++ this.d = attributeprovider; ++ } ++ ++ private void a(AttributeModifiable attributemodifiable) { ++ if (attributemodifiable.getAttribute().b()) { ++ this.c.add(attributemodifiable); ++ } ++ ++ } ++ ++ public Set getAttributes() { ++ return this.c; ++ } ++ ++ public Collection b() { ++ return (Collection) this.b.values().stream().filter((attributemodifiable) -> { ++ return attributemodifiable.getAttribute().b(); ++ }).collect(Collectors.toList()); ++ } ++ ++ @Nullable ++ public AttributeModifiable a(AttributeBase attributebase) { ++ return (AttributeModifiable) this.b.computeIfAbsent(attributebase, (attributebase1) -> { ++ return this.d.a(this::a, attributebase1); ++ }); ++ } ++ ++ public boolean b(AttributeBase attributebase) { ++ return this.b.get(attributebase) != null || this.d.c(attributebase); ++ } ++ ++ public boolean a(AttributeBase attributebase, UUID uuid) { ++ AttributeModifiable attributemodifiable = (AttributeModifiable) this.b.get(attributebase); ++ ++ return attributemodifiable != null ? attributemodifiable.a(uuid) != null : this.d.b(attributebase, uuid); ++ } ++ ++ public double c(AttributeBase attributebase) { ++ AttributeModifiable attributemodifiable = (AttributeModifiable) this.b.get(attributebase); ++ ++ return attributemodifiable != null ? attributemodifiable.getValue() : this.d.a(attributebase); ++ } ++ ++ public double d(AttributeBase attributebase) { ++ AttributeModifiable attributemodifiable = (AttributeModifiable) this.b.get(attributebase); ++ ++ return attributemodifiable != null ? attributemodifiable.getBaseValue() : this.d.b(attributebase); ++ } ++ ++ public double b(AttributeBase attributebase, UUID uuid) { ++ AttributeModifiable attributemodifiable = (AttributeModifiable) this.b.get(attributebase); ++ ++ return attributemodifiable != null ? attributemodifiable.a(uuid).getAmount() : this.d.a(attributebase, uuid); ++ } ++ ++ public void a(Multimap multimap) { ++ multimap.asMap().forEach((attributebase, collection) -> { ++ AttributeModifiable attributemodifiable = (AttributeModifiable) this.b.get(attributebase); ++ ++ if (attributemodifiable != null) { ++ collection.forEach(attributemodifiable::removeModifier); ++ } ++ ++ }); ++ } ++ ++ public void b(Multimap multimap) { ++ multimap.forEach((attributebase, attributemodifier) -> { ++ AttributeModifiable attributemodifiable = this.a(attributebase); ++ ++ if (attributemodifiable != null) { ++ attributemodifiable.removeModifier(attributemodifier); ++ attributemodifiable.b(attributemodifier); ++ } ++ ++ }); ++ } ++ ++ public NBTTagList c() { ++ NBTTagList nbttaglist = new NBTTagList(); ++ Iterator iterator = this.b.values().iterator(); ++ ++ while (iterator.hasNext()) { ++ AttributeModifiable attributemodifiable = (AttributeModifiable) iterator.next(); ++ ++ nbttaglist.add(attributemodifiable.g()); ++ } ++ ++ return nbttaglist; ++ } ++ ++ public void a(NBTTagList nbttaglist) { ++ for (int i = 0; i < nbttaglist.size(); ++i) { ++ NBTTagCompound nbttagcompound = nbttaglist.getCompound(i); ++ String s = nbttagcompound.getString("Name"); ++ ++ SystemUtils.a(IRegistry.ATTRIBUTE.getOptional(MinecraftKey.a(s)), (attributebase) -> { ++ AttributeModifiable attributemodifiable = this.a(attributebase); ++ ++ if (attributemodifiable != null) { ++ attributemodifiable.a(nbttagcompound); ++ } ++ ++ }, () -> { ++ AttributeMapBase.LOGGER.warn("Ignoring unknown attribute '{}'", s); ++ }); ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a30b92736d8b36f750eb721d4a056bdfc98845b5 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java +@@ -0,0 +1,113 @@ ++package net.minecraft.world.entity.ai.behavior; ++ ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Map.Entry; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import net.minecraft.world.entity.ai.memory.MemoryStatus; ++ ++public abstract class Behavior { ++ ++ protected final Map, MemoryStatus> a; ++ private Behavior.Status b; ++ private long c; ++ private final int d; ++ private final int e; ++ ++ public Behavior(Map, MemoryStatus> map) { ++ this(map, 60); ++ } ++ ++ public Behavior(Map, MemoryStatus> map, int i) { ++ this(map, i, i); ++ } ++ ++ public Behavior(Map, MemoryStatus> map, int i, int j) { ++ this.b = Behavior.Status.STOPPED; ++ this.d = i; ++ this.e = j; ++ this.a = map; ++ } ++ ++ public Behavior.Status a() { ++ return this.b; ++ } ++ ++ public final boolean e(WorldServer worldserver, E e0, long i) { ++ if (this.a(e0) && this.a(worldserver, e0)) { ++ this.b = Behavior.Status.RUNNING; ++ int j = this.d + worldserver.getRandom().nextInt(this.e + 1 - this.d); ++ ++ this.c = i + (long) j; ++ this.a(worldserver, e0, i); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ protected void a(WorldServer worldserver, E e0, long i) {} ++ ++ public final void f(WorldServer worldserver, E e0, long i) { ++ if (!this.a(i) && this.b(worldserver, e0, i)) { ++ this.d(worldserver, e0, i); ++ } else { ++ this.g(worldserver, e0, i); ++ } ++ ++ } ++ ++ protected void d(WorldServer worldserver, E e0, long i) {} ++ ++ public final void g(WorldServer worldserver, E e0, long i) { ++ this.b = Behavior.Status.STOPPED; ++ this.c(worldserver, e0, i); ++ } ++ ++ protected void c(WorldServer worldserver, E e0, long i) {} ++ ++ protected boolean b(WorldServer worldserver, E e0, long i) { ++ return false; ++ } ++ ++ protected boolean a(long i) { ++ return i > this.c; ++ } ++ ++ protected boolean a(WorldServer worldserver, E e0) { ++ return true; ++ } ++ ++ public String toString() { ++ return this.getClass().getSimpleName(); ++ } ++ ++ private boolean a(E e0) { ++ Iterator iterator = this.a.entrySet().iterator(); ++ ++ MemoryModuleType memorymoduletype; ++ MemoryStatus memorystatus; ++ ++ do { ++ if (!iterator.hasNext()) { ++ return true; ++ } ++ ++ Entry, MemoryStatus> entry = (Entry) iterator.next(); ++ ++ memorymoduletype = (MemoryModuleType) entry.getKey(); ++ memorystatus = (MemoryStatus) entry.getValue(); ++ } while (e0.getBehaviorController().a(memorymoduletype, memorystatus)); ++ ++ return false; ++ } ++ ++ public static enum Status { ++ ++ STOPPED, RUNNING; ++ ++ private Status() {} ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorFindPosition.java b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorFindPosition.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a04d4dc665f34687b5d744fea56bc46263f27235 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorFindPosition.java +@@ -0,0 +1,150 @@ ++package net.minecraft.world.entity.ai.behavior; ++ ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.ImmutableMap.Builder; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import java.util.Iterator; ++import java.util.Optional; ++import java.util.Random; ++import java.util.Set; ++import java.util.function.Predicate; ++import java.util.stream.Collectors; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.GlobalPos; ++import net.minecraft.network.protocol.game.PacketDebug; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.EntityCreature; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import net.minecraft.world.entity.ai.memory.MemoryStatus; ++import net.minecraft.world.entity.ai.village.poi.VillagePlace; ++import net.minecraft.world.entity.ai.village.poi.VillagePlaceType; ++import net.minecraft.world.level.pathfinder.PathEntity; ++ ++public class BehaviorFindPosition extends Behavior { ++ ++ private final VillagePlaceType b; ++ private final MemoryModuleType c; ++ private final boolean d; ++ private final Optional e; ++ private long f; ++ private final Long2ObjectMap g; ++ ++ public BehaviorFindPosition(VillagePlaceType villageplacetype, MemoryModuleType memorymoduletype, MemoryModuleType memorymoduletype1, boolean flag, Optional optional) { ++ super(a(memorymoduletype, memorymoduletype1)); ++ this.g = new Long2ObjectOpenHashMap(); ++ this.b = villageplacetype; ++ this.c = memorymoduletype1; ++ this.d = flag; ++ this.e = optional; ++ } ++ ++ public BehaviorFindPosition(VillagePlaceType villageplacetype, MemoryModuleType memorymoduletype, boolean flag, Optional optional) { ++ this(villageplacetype, memorymoduletype, memorymoduletype, flag, optional); ++ } ++ ++ private static ImmutableMap, MemoryStatus> a(MemoryModuleType memorymoduletype, MemoryModuleType memorymoduletype1) { ++ Builder, MemoryStatus> builder = ImmutableMap.builder(); ++ ++ builder.put(memorymoduletype, MemoryStatus.VALUE_ABSENT); ++ if (memorymoduletype1 != memorymoduletype) { ++ builder.put(memorymoduletype1, MemoryStatus.VALUE_ABSENT); ++ } ++ ++ return builder.build(); ++ } ++ ++ protected boolean a(WorldServer worldserver, EntityCreature entitycreature) { ++ if (this.d && entitycreature.isBaby()) { ++ return false; ++ } else if (this.f == 0L) { ++ this.f = entitycreature.world.getTime() + (long) worldserver.random.nextInt(20); ++ return false; ++ } else { ++ return worldserver.getTime() >= this.f; ++ } ++ } ++ ++ protected void a(WorldServer worldserver, EntityCreature entitycreature, long i) { ++ this.f = i + 20L + (long) worldserver.getRandom().nextInt(20); ++ VillagePlace villageplace = worldserver.y(); ++ ++ this.g.long2ObjectEntrySet().removeIf((entry) -> { ++ return !((BehaviorFindPosition.a) entry.getValue()).b(i); ++ }); ++ Predicate predicate = (blockposition) -> { ++ BehaviorFindPosition.a behaviorfindposition_a = (BehaviorFindPosition.a) this.g.get(blockposition.asLong()); ++ ++ if (behaviorfindposition_a == null) { ++ return true; ++ } else if (!behaviorfindposition_a.c(i)) { ++ return false; ++ } else { ++ behaviorfindposition_a.a(i); ++ return true; ++ } ++ }; ++ Set set = (Set) villageplace.b(this.b.c(), predicate, entitycreature.getChunkCoordinates(), 48, VillagePlace.Occupancy.HAS_SPACE).limit(5L).collect(Collectors.toSet()); ++ PathEntity pathentity = entitycreature.getNavigation().a(set, this.b.d()); ++ ++ if (pathentity != null && pathentity.j()) { ++ BlockPosition blockposition = pathentity.m(); ++ ++ villageplace.c(blockposition).ifPresent((villageplacetype) -> { ++ villageplace.a(this.b.c(), (blockposition1) -> { ++ return blockposition1.equals(blockposition); ++ }, blockposition, 1); ++ entitycreature.getBehaviorController().setMemory(this.c, (Object) GlobalPos.create(worldserver.getDimensionKey(), blockposition)); ++ this.e.ifPresent((obyte) -> { ++ worldserver.broadcastEntityEffect(entitycreature, obyte); ++ }); ++ this.g.clear(); ++ PacketDebug.c(worldserver, blockposition); ++ }); ++ } else { ++ Iterator iterator = set.iterator(); ++ ++ while (iterator.hasNext()) { ++ BlockPosition blockposition1 = (BlockPosition) iterator.next(); ++ ++ this.g.computeIfAbsent(blockposition1.asLong(), (j) -> { ++ return new BehaviorFindPosition.a(entitycreature.world.random, i); ++ }); ++ } ++ } ++ ++ } ++ ++ static class a { ++ ++ private final Random a; ++ private long b; ++ private long c; ++ private int d; ++ ++ a(Random random, long i) { ++ this.a = random; ++ this.a(i); ++ } ++ ++ public void a(long i) { ++ this.b = i; ++ int j = this.d + this.a.nextInt(40) + 40; ++ ++ this.d = Math.min(j, 400); ++ this.c = i + (long) this.d; ++ } ++ ++ public boolean b(long i) { ++ return i - this.b < 400L; ++ } ++ ++ public boolean c(long i) { ++ return i >= this.c; ++ } ++ ++ public String toString() { ++ return "RetryMarker{, previousAttemptAt=" + this.b + ", nextScheduledAttemptAt=" + this.c + ", currentDelay=" + this.d + '}'; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorGate.java b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorGate.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dc926f7e59fa350902d4a24aefc3df3eac7d75db +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorGate.java +@@ -0,0 +1,126 @@ ++package net.minecraft.world.entity.ai.behavior; ++ ++import com.mojang.datafixers.util.Pair; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.function.Consumer; ++import java.util.stream.Collectors; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.ai.BehaviorController; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import net.minecraft.world.entity.ai.memory.MemoryStatus; ++ ++public class BehaviorGate extends Behavior { ++ ++ private final Set> b; ++ private final BehaviorGate.Order c; ++ private final BehaviorGate.Execution d; ++ private final WeightedList> e = new WeightedList<>(); ++ ++ public BehaviorGate(Map, MemoryStatus> map, Set> set, BehaviorGate.Order behaviorgate_order, BehaviorGate.Execution behaviorgate_execution, List, Integer>> list) { ++ super(map); ++ this.b = set; ++ this.c = behaviorgate_order; ++ this.d = behaviorgate_execution; ++ list.forEach((pair) -> { ++ this.e.a(pair.getFirst(), (Integer) pair.getSecond()); ++ }); ++ } ++ ++ @Override ++ protected boolean b(WorldServer worldserver, E e0, long i) { ++ return this.e.c().filter((behavior) -> { ++ return behavior.a() == Behavior.Status.RUNNING; ++ }).anyMatch((behavior) -> { ++ return behavior.b(worldserver, e0, i); ++ }); ++ } ++ ++ @Override ++ protected boolean a(long i) { ++ return false; ++ } ++ ++ @Override ++ protected void a(WorldServer worldserver, E e0, long i) { ++ this.c.a(this.e); ++ this.d.a(this.e, worldserver, e0, i); ++ } ++ ++ @Override ++ protected void d(WorldServer worldserver, E e0, long i) { ++ this.e.c().filter((behavior) -> { ++ return behavior.a() == Behavior.Status.RUNNING; ++ }).forEach((behavior) -> { ++ behavior.f(worldserver, e0, i); ++ }); ++ } ++ ++ @Override ++ protected void c(WorldServer worldserver, E e0, long i) { ++ this.e.c().filter((behavior) -> { ++ return behavior.a() == Behavior.Status.RUNNING; ++ }).forEach((behavior) -> { ++ behavior.g(worldserver, e0, i); ++ }); ++ Set set = this.b; ++ BehaviorController behaviorcontroller = e0.getBehaviorController(); ++ ++ set.forEach(behaviorcontroller::removeMemory); ++ } ++ ++ @Override ++ public String toString() { ++ Set> set = (Set) this.e.c().filter((behavior) -> { ++ return behavior.a() == Behavior.Status.RUNNING; ++ }).collect(Collectors.toSet()); ++ ++ return "(" + this.getClass().getSimpleName() + "): " + set; ++ } ++ ++ static enum Execution { ++ ++ RUN_ONE { ++ @Override ++ public void a(WeightedList> weightedlist, WorldServer worldserver, E e0, long i) { ++ weightedlist.c().filter((behavior) -> { ++ return behavior.a() == Behavior.Status.STOPPED; ++ }).filter((behavior) -> { ++ return behavior.e(worldserver, e0, i); ++ }).findFirst(); ++ } ++ }, ++ TRY_ALL { ++ @Override ++ public void a(WeightedList> weightedlist, WorldServer worldserver, E e0, long i) { ++ weightedlist.c().filter((behavior) -> { ++ return behavior.a() == Behavior.Status.STOPPED; ++ }).forEach((behavior) -> { ++ behavior.e(worldserver, e0, i); ++ }); ++ } ++ }; ++ ++ private Execution() {} ++ ++ public abstract void a(WeightedList> weightedlist, WorldServer worldserver, E e0, long i); ++ } ++ ++ static enum Order { ++ ++ ORDERED((weightedlist) -> { ++ }), SHUFFLED(WeightedList::a); ++ ++ private final Consumer> c; ++ ++ private Order(Consumer consumer) { ++ this.c = consumer; ++ } ++ ++ public void a(WeightedList weightedlist) { ++ this.c.accept(weightedlist); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorLookInteract.java b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorLookInteract.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f56072c77b9dfd0eeafb7a6970eecf593315f63e +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorLookInteract.java +@@ -0,0 +1,64 @@ ++package net.minecraft.world.entity.ai.behavior; ++ ++import com.google.common.collect.ImmutableMap; ++import java.util.List; ++import java.util.function.Predicate; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.ai.BehaviorController; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import net.minecraft.world.entity.ai.memory.MemoryStatus; ++ ++public class BehaviorLookInteract extends Behavior { ++ ++ private final EntityTypes b; ++ private final int c; ++ private final Predicate d; ++ private final Predicate e; ++ ++ public BehaviorLookInteract(EntityTypes entitytypes, int i, Predicate predicate, Predicate predicate1) { ++ super(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED, MemoryModuleType.INTERACTION_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.VISIBLE_MOBS, MemoryStatus.VALUE_PRESENT)); ++ this.b = entitytypes; ++ this.c = i * i; ++ this.d = predicate1; ++ this.e = predicate; ++ } ++ ++ public BehaviorLookInteract(EntityTypes entitytypes, int i) { ++ this(entitytypes, i, (entityliving) -> { ++ return true; ++ }, (entityliving) -> { ++ return true; ++ }); ++ } ++ ++ @Override ++ public boolean a(WorldServer worldserver, EntityLiving entityliving) { ++ return this.e.test(entityliving) && this.b(entityliving).stream().anyMatch(this::a); ++ } ++ ++ @Override ++ public void a(WorldServer worldserver, EntityLiving entityliving, long i) { ++ super.a(worldserver, entityliving, i); ++ BehaviorController behaviorcontroller = entityliving.getBehaviorController(); ++ ++ behaviorcontroller.getMemory(MemoryModuleType.VISIBLE_MOBS).ifPresent((list) -> { ++ list.stream().filter((entityliving1) -> { ++ return entityliving1.h((Entity) entityliving) <= (double) this.c; ++ }).filter(this::a).findFirst().ifPresent((entityliving1) -> { ++ behaviorcontroller.setMemory(MemoryModuleType.INTERACTION_TARGET, (Object) entityliving1); ++ behaviorcontroller.setMemory(MemoryModuleType.LOOK_TARGET, (Object) (new BehaviorPositionEntity(entityliving1, true))); ++ }); ++ }); ++ } ++ ++ private boolean a(EntityLiving entityliving) { ++ return this.b.equals(entityliving.getEntityType()) && this.d.test(entityliving); ++ } ++ ++ private List b(EntityLiving entityliving) { ++ return (List) entityliving.getBehaviorController().getMemory(MemoryModuleType.VISIBLE_MOBS).get(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorSleep.java b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorSleep.java +new file mode 100644 +index 0000000000000000000000000000000000000000..84eba4c91e8e608b84623d6c71233e2512b77a54 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorSleep.java +@@ -0,0 +1,91 @@ ++package net.minecraft.world.entity.ai.behavior; ++ ++import com.google.common.collect.ImmutableMap; ++import java.util.Optional; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.GlobalPos; ++import net.minecraft.core.IPosition; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsBlock; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.ai.BehaviorController; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import net.minecraft.world.entity.ai.memory.MemoryStatus; ++import net.minecraft.world.entity.schedule.Activity; ++import net.minecraft.world.level.block.BlockBed; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.pathfinder.PathPoint; ++ ++public class BehaviorSleep extends Behavior { ++ ++ private long b; ++ ++ public BehaviorSleep() { ++ super(ImmutableMap.of(MemoryModuleType.HOME, MemoryStatus.VALUE_PRESENT, MemoryModuleType.LAST_WOKEN, MemoryStatus.REGISTERED)); ++ } ++ ++ @Override ++ protected boolean a(WorldServer worldserver, EntityLiving entityliving) { ++ if (entityliving.isPassenger()) { ++ return false; ++ } else { ++ BehaviorController behaviorcontroller = entityliving.getBehaviorController(); ++ GlobalPos globalpos = (GlobalPos) behaviorcontroller.getMemory(MemoryModuleType.HOME).get(); ++ ++ if (worldserver.getDimensionKey() != globalpos.getDimensionManager()) { ++ return false; ++ } else { ++ Optional optional = behaviorcontroller.getMemory(MemoryModuleType.LAST_WOKEN); ++ ++ if (optional.isPresent()) { ++ long i = worldserver.getTime() - (Long) optional.get(); ++ ++ if (i > 0L && i < 100L) { ++ return false; ++ } ++ } ++ ++ IBlockData iblockdata = worldserver.getType(globalpos.getBlockPosition()); ++ ++ return globalpos.getBlockPosition().a((IPosition) entityliving.getPositionVector(), 2.0D) && iblockdata.getBlock().a((Tag) TagsBlock.BEDS) && !(Boolean) iblockdata.get(BlockBed.OCCUPIED); ++ } ++ } ++ } ++ ++ @Override ++ protected boolean b(WorldServer worldserver, EntityLiving entityliving, long i) { ++ Optional optional = entityliving.getBehaviorController().getMemory(MemoryModuleType.HOME); ++ ++ if (!optional.isPresent()) { ++ return false; ++ } else { ++ BlockPosition blockposition = ((GlobalPos) optional.get()).getBlockPosition(); ++ ++ return entityliving.getBehaviorController().c(Activity.REST) && entityliving.locY() > (double) blockposition.getY() + 0.4D && blockposition.a((IPosition) entityliving.getPositionVector(), 1.14D); ++ } ++ } ++ ++ @Override ++ protected void a(WorldServer worldserver, EntityLiving entityliving, long i) { ++ if (i > this.b) { ++ BehaviorInteractDoor.a(worldserver, entityliving, (PathPoint) null, (PathPoint) null); ++ entityliving.entitySleep(((GlobalPos) entityliving.getBehaviorController().getMemory(MemoryModuleType.HOME).get()).getBlockPosition()); ++ } ++ ++ } ++ ++ @Override ++ protected boolean a(long i) { ++ return false; ++ } ++ ++ @Override ++ protected void c(WorldServer worldserver, EntityLiving entityliving, long i) { ++ if (entityliving.isSleeping()) { ++ entityliving.entityWakeup(); ++ this.b = i + 40L; ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f6f8c68ff3642e28901094e8b501fcf8ec2cecd7 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java +@@ -0,0 +1,118 @@ ++package net.minecraft.world.entity.ai.behavior; ++ ++import com.google.common.collect.Lists; ++import com.mojang.datafixers.util.Pair; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.DataResult; ++import com.mojang.serialization.Dynamic; ++import com.mojang.serialization.DynamicOps; ++import com.mojang.serialization.OptionalDynamic; ++import java.util.Comparator; ++import java.util.List; ++import java.util.Random; ++import java.util.stream.Stream; ++ ++public class WeightedList { ++ ++ protected final List> a; ++ private final Random b; ++ ++ public WeightedList() { ++ this(Lists.newArrayList()); ++ } ++ ++ private WeightedList(List> list) { ++ this.b = new Random(); ++ this.a = Lists.newArrayList(list); ++ } ++ ++ public static Codec> a(Codec codec) { ++ return WeightedList.a.a(codec).listOf().xmap(WeightedList::new, (weightedlist) -> { ++ return weightedlist.a; ++ }); ++ } ++ ++ public WeightedList a(U u0, int i) { ++ this.a.add(new WeightedList.a<>(u0, i)); ++ return this; ++ } ++ ++ public WeightedList a() { ++ return this.a(this.b); ++ } ++ ++ public WeightedList a(Random random) { ++ this.a.forEach((weightedlist_a) -> { ++ weightedlist_a.a(random.nextFloat()); ++ }); ++ this.a.sort(Comparator.comparingDouble((object) -> { ++ return ((WeightedList.a) object).c(); ++ })); ++ return this; ++ } ++ ++ public boolean b() { ++ return this.a.isEmpty(); ++ } ++ ++ public Stream c() { ++ return this.a.stream().map(WeightedList.a::a); ++ } ++ ++ public U b(Random random) { ++ return this.a(random).c().findFirst().orElseThrow(RuntimeException::new); ++ } ++ ++ public String toString() { ++ return "WeightedList[" + this.a + "]"; ++ } ++ ++ public static class a { ++ ++ private final T a; ++ private final int b; ++ private double c; ++ ++ private a(T t0, int i) { ++ this.b = i; ++ this.a = t0; ++ } ++ ++ private double c() { ++ return this.c; ++ } ++ ++ private void a(float f) { ++ this.c = -Math.pow((double) f, (double) (1.0F / (float) this.b)); ++ } ++ ++ public T a() { ++ return this.a; ++ } ++ ++ public String toString() { ++ return "" + this.b + ":" + this.a; ++ } ++ ++ public static Codec> a(final Codec codec) { ++ return new Codec>() { ++ public DataResult, T>> decode(DynamicOps dynamicops, T t0) { ++ Dynamic dynamic = new Dynamic(dynamicops, t0); ++ OptionalDynamic optionaldynamic = dynamic.get("data"); ++ Codec codec1 = codec; ++ ++ codec.getClass(); ++ return optionaldynamic.flatMap(codec1::parse).map((object) -> { ++ return new WeightedList.a<>(object, dynamic.get("weight").asInt(1)); ++ }).map((weightedlist_a) -> { ++ return Pair.of(weightedlist_a, dynamicops.empty()); ++ }); ++ } ++ ++ public DataResult encode(WeightedList.a weightedlist_a, DynamicOps dynamicops, T t0) { ++ return dynamicops.mapBuilder().add("weight", dynamicops.createInt(weightedlist_a.b)).add("data", codec.encodeStart(dynamicops, weightedlist_a.a)).build(t0); ++ } ++ }; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/control/ControllerJump.java b/src/main/java/net/minecraft/world/entity/ai/control/ControllerJump.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9767ac416fcd60a8a57b648dcb3f1e427bacd54d +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/control/ControllerJump.java +@@ -0,0 +1,22 @@ ++package net.minecraft.world.entity.ai.control; ++ ++import net.minecraft.world.entity.EntityInsentient; ++ ++public class ControllerJump { ++ ++ private final EntityInsentient b; ++ protected boolean a; ++ ++ public ControllerJump(EntityInsentient entityinsentient) { ++ this.b = entityinsentient; ++ } ++ ++ public void jump() { ++ this.a = true; ++ } ++ ++ public void b() { ++ this.b.setJumping(this.a); ++ this.a = false; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/control/ControllerMove.java b/src/main/java/net/minecraft/world/entity/ai/control/ControllerMove.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4044861622294a317fef7e93aa86e96e8474b513 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/control/ControllerMove.java +@@ -0,0 +1,179 @@ ++package net.minecraft.world.entity.ai.control; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsBlock; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; ++import net.minecraft.world.entity.ai.navigation.NavigationAbstract; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.pathfinder.PathType; ++import net.minecraft.world.level.pathfinder.PathfinderAbstract; ++import net.minecraft.world.phys.shapes.VoxelShape; ++ ++public class ControllerMove { ++ ++ protected final EntityInsentient a; ++ protected double b; ++ protected double c; ++ protected double d; ++ protected double e; ++ protected float f; ++ protected float g; ++ protected ControllerMove.Operation h; ++ ++ public ControllerMove(EntityInsentient entityinsentient) { ++ this.h = ControllerMove.Operation.WAIT; ++ this.a = entityinsentient; ++ } ++ ++ public boolean b() { ++ return this.h == ControllerMove.Operation.MOVE_TO; ++ } ++ ++ public double c() { ++ return this.e; ++ } ++ ++ public void a(double d0, double d1, double d2, double d3) { ++ this.b = d0; ++ this.c = d1; ++ this.d = d2; ++ this.e = d3; ++ if (this.h != ControllerMove.Operation.JUMPING) { ++ this.h = ControllerMove.Operation.MOVE_TO; ++ } ++ ++ } ++ ++ public void a(float f, float f1) { ++ this.h = ControllerMove.Operation.STRAFE; ++ this.f = f; ++ this.g = f1; ++ this.e = 0.25D; ++ } ++ ++ public void a() { ++ float f; ++ ++ if (this.h == ControllerMove.Operation.STRAFE) { ++ float f1 = (float) this.a.b(GenericAttributes.MOVEMENT_SPEED); ++ float f2 = (float) this.e * f1; ++ float f3 = this.f; ++ float f4 = this.g; ++ float f5 = MathHelper.c(f3 * f3 + f4 * f4); ++ ++ if (f5 < 1.0F) { ++ f5 = 1.0F; ++ } ++ ++ f5 = f2 / f5; ++ f3 *= f5; ++ f4 *= f5; ++ float f6 = MathHelper.sin(this.a.yaw * 0.017453292F); ++ float f7 = MathHelper.cos(this.a.yaw * 0.017453292F); ++ float f8 = f3 * f7 - f4 * f6; ++ ++ f = f4 * f7 + f3 * f6; ++ if (!this.b(f8, f)) { ++ this.f = 1.0F; ++ this.g = 0.0F; ++ } ++ ++ this.a.q(f2); ++ this.a.t(this.f); ++ this.a.v(this.g); ++ this.h = ControllerMove.Operation.WAIT; ++ } else if (this.h == ControllerMove.Operation.MOVE_TO) { ++ this.h = ControllerMove.Operation.WAIT; ++ double d0 = this.b - this.a.locX(); ++ double d1 = this.d - this.a.locZ(); ++ double d2 = this.c - this.a.locY(); ++ double d3 = d0 * d0 + d2 * d2 + d1 * d1; ++ ++ if (d3 < 2.500000277905201E-7D) { ++ this.a.t(0.0F); ++ return; ++ } ++ ++ f = (float) (MathHelper.d(d1, d0) * 57.2957763671875D) - 90.0F; ++ this.a.yaw = this.a(this.a.yaw, f, 90.0F); ++ this.a.q((float) (this.e * this.a.b(GenericAttributes.MOVEMENT_SPEED))); ++ BlockPosition blockposition = this.a.getChunkCoordinates(); ++ IBlockData iblockdata = this.a.world.getType(blockposition); ++ Block block = iblockdata.getBlock(); ++ VoxelShape voxelshape = iblockdata.getCollisionShape(this.a.world, blockposition); ++ ++ if (d2 > (double) this.a.G && d0 * d0 + d1 * d1 < (double) Math.max(1.0F, this.a.getWidth()) || !voxelshape.isEmpty() && this.a.locY() < voxelshape.c(EnumDirection.EnumAxis.Y) + (double) blockposition.getY() && !block.a((Tag) TagsBlock.DOORS) && !block.a((Tag) TagsBlock.FENCES)) { ++ this.a.getControllerJump().jump(); ++ this.h = ControllerMove.Operation.JUMPING; ++ } ++ } else if (this.h == ControllerMove.Operation.JUMPING) { ++ this.a.q((float) (this.e * this.a.b(GenericAttributes.MOVEMENT_SPEED))); ++ if (this.a.isOnGround()) { ++ this.h = ControllerMove.Operation.WAIT; ++ } ++ } else { ++ this.a.t(0.0F); ++ } ++ ++ } ++ ++ private boolean b(float f, float f1) { ++ NavigationAbstract navigationabstract = this.a.getNavigation(); ++ ++ if (navigationabstract != null) { ++ PathfinderAbstract pathfinderabstract = navigationabstract.q(); ++ ++ if (pathfinderabstract != null && pathfinderabstract.a(this.a.world, MathHelper.floor(this.a.locX() + (double) f), MathHelper.floor(this.a.locY()), MathHelper.floor(this.a.locZ() + (double) f1)) != PathType.WALKABLE) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ protected float a(float f, float f1, float f2) { ++ float f3 = MathHelper.g(f1 - f); ++ ++ if (f3 > f2) { ++ f3 = f2; ++ } ++ ++ if (f3 < -f2) { ++ f3 = -f2; ++ } ++ ++ float f4 = f + f3; ++ ++ if (f4 < 0.0F) { ++ f4 += 360.0F; ++ } else if (f4 > 360.0F) { ++ f4 -= 360.0F; ++ } ++ ++ return f4; ++ } ++ ++ public double d() { ++ return this.b; ++ } ++ ++ public double e() { ++ return this.c; ++ } ++ ++ public double f() { ++ return this.d; ++ } ++ ++ public static enum Operation { ++ ++ WAIT, MOVE_TO, STRAFE, JUMPING; ++ ++ private Operation() {} ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/control/ControllerMoveFlying.java b/src/main/java/net/minecraft/world/entity/ai/control/ControllerMoveFlying.java +new file mode 100644 +index 0000000000000000000000000000000000000000..80cba36bc59e89c40c96ca556594a4285f06fc6f +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/control/ControllerMoveFlying.java +@@ -0,0 +1,61 @@ ++package net.minecraft.world.entity.ai.control; ++ ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; ++ ++public class ControllerMoveFlying extends ControllerMove { ++ ++ private final int i; ++ private final boolean j; ++ ++ public ControllerMoveFlying(EntityInsentient entityinsentient, int i, boolean flag) { ++ super(entityinsentient); ++ this.i = i; ++ this.j = flag; ++ } ++ ++ @Override ++ public void a() { ++ if (this.h == ControllerMove.Operation.MOVE_TO) { ++ this.h = ControllerMove.Operation.WAIT; ++ this.a.setNoGravity(true); ++ double d0 = this.b - this.a.locX(); ++ double d1 = this.c - this.a.locY(); ++ double d2 = this.d - this.a.locZ(); ++ double d3 = d0 * d0 + d1 * d1 + d2 * d2; ++ ++ if (d3 < 2.500000277905201E-7D) { ++ this.a.u(0.0F); ++ this.a.t(0.0F); ++ return; ++ } ++ ++ float f = (float) (MathHelper.d(d2, d0) * 57.2957763671875D) - 90.0F; ++ ++ this.a.yaw = this.a(this.a.yaw, f, 90.0F); ++ float f1; ++ ++ if (this.a.isOnGround()) { ++ f1 = (float) (this.e * this.a.b(GenericAttributes.MOVEMENT_SPEED)); ++ } else { ++ f1 = (float) (this.e * this.a.b(GenericAttributes.FLYING_SPEED)); ++ } ++ ++ this.a.q(f1); ++ double d4 = (double) MathHelper.sqrt(d0 * d0 + d2 * d2); ++ float f2 = (float) (-(MathHelper.d(d1, d4) * 57.2957763671875D)); ++ ++ this.a.pitch = this.a(this.a.pitch, f2, (float) this.i); ++ this.a.u(d1 > 0.0D ? f1 : -f1); ++ } else { ++ if (!this.j) { ++ this.a.setNoGravity(false); ++ } ++ ++ this.a.u(0.0F); ++ this.a.t(0.0F); ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5e599d88a150c907f50acbb58ad1725c3fe361e4 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java +@@ -0,0 +1,46 @@ ++package net.minecraft.world.entity.ai.goal; ++ ++import java.util.EnumSet; ++ ++public abstract class PathfinderGoal { ++ ++ private final EnumSet a = EnumSet.noneOf(PathfinderGoal.Type.class); ++ ++ public PathfinderGoal() {} ++ ++ public abstract boolean a(); ++ ++ public boolean b() { ++ return this.a(); ++ } ++ ++ public boolean C_() { ++ return true; ++ } ++ ++ public void c() {} ++ ++ public void d() {} ++ ++ public void e() {} ++ ++ public void a(EnumSet enumset) { ++ this.a.clear(); ++ this.a.addAll(enumset); ++ } ++ ++ public String toString() { ++ return this.getClass().getSimpleName(); ++ } ++ ++ public EnumSet i() { ++ return this.a; ++ } ++ ++ public static enum Type { ++ ++ MOVE, LOOK, JUMP, TARGET; ++ ++ private Type() {} ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalFloat.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalFloat.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8dfa1a6ade7f51e5d68b290f5376d999bb4c60ab +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalFloat.java +@@ -0,0 +1,30 @@ ++package net.minecraft.world.entity.ai.goal; ++ ++import java.util.EnumSet; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsFluid; ++import net.minecraft.world.entity.EntityInsentient; ++ ++public class PathfinderGoalFloat extends PathfinderGoal { ++ ++ private final EntityInsentient a; ++ ++ public PathfinderGoalFloat(EntityInsentient entityinsentient) { ++ this.a = entityinsentient; ++ this.a(EnumSet.of(PathfinderGoal.Type.JUMP)); ++ entityinsentient.getNavigation().d(true); ++ } ++ ++ @Override ++ public boolean a() { ++ return this.a.isInWater() && this.a.b((Tag) TagsFluid.WATER) > this.a.cx() || this.a.aQ(); ++ } ++ ++ @Override ++ public void e() { ++ if (this.a.getRandom().nextFloat() < 0.8F) { ++ this.a.getControllerJump().jump(); ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalGotoTarget.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalGotoTarget.java +new file mode 100644 +index 0000000000000000000000000000000000000000..62276550627bfe453794a2b3101426fe05a585ff +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalGotoTarget.java +@@ -0,0 +1,125 @@ ++package net.minecraft.world.entity.ai.goal; ++ ++import java.util.EnumSet; ++import net.minecraft.core.BaseBlockPosition; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.IPosition; ++import net.minecraft.world.entity.EntityCreature; ++import net.minecraft.world.level.IWorldReader; ++ ++public abstract class PathfinderGoalGotoTarget extends PathfinderGoal { ++ ++ protected final EntityCreature a; ++ public final double b; ++ protected int c; ++ protected int d; ++ private int g; ++ protected BlockPosition e; ++ private boolean h; ++ private final int i; ++ private final int j; ++ protected int f; ++ ++ public PathfinderGoalGotoTarget(EntityCreature entitycreature, double d0, int i) { ++ this(entitycreature, d0, i, 1); ++ } ++ ++ public PathfinderGoalGotoTarget(EntityCreature entitycreature, double d0, int i, int j) { ++ this.e = BlockPosition.ZERO; ++ this.a = entitycreature; ++ this.b = d0; ++ this.i = i; ++ this.f = 0; ++ this.j = j; ++ this.a(EnumSet.of(PathfinderGoal.Type.MOVE, PathfinderGoal.Type.JUMP)); ++ } ++ ++ @Override ++ public boolean a() { ++ if (this.c > 0) { ++ --this.c; ++ return false; ++ } else { ++ this.c = this.a(this.a); ++ return this.m(); ++ } ++ } ++ ++ protected int a(EntityCreature entitycreature) { ++ return 200 + entitycreature.getRandom().nextInt(200); ++ } ++ ++ @Override ++ public boolean b() { ++ return this.d >= -this.g && this.d <= 1200 && this.a(this.a.world, this.e); ++ } ++ ++ @Override ++ public void c() { ++ this.g(); ++ this.d = 0; ++ this.g = this.a.getRandom().nextInt(this.a.getRandom().nextInt(1200) + 1200) + 1200; ++ } ++ ++ protected void g() { ++ this.a.getNavigation().a((double) ((float) this.e.getX()) + 0.5D, (double) (this.e.getY() + 1), (double) ((float) this.e.getZ()) + 0.5D, this.b); ++ } ++ ++ public double h() { ++ return 1.0D; ++ } ++ ++ protected BlockPosition j() { ++ return this.e.up(); ++ } ++ ++ @Override ++ public void e() { ++ BlockPosition blockposition = this.j(); ++ ++ if (!blockposition.a((IPosition) this.a.getPositionVector(), this.h())) { ++ this.h = false; ++ ++this.d; ++ if (this.k()) { ++ this.a.getNavigation().a((double) ((float) blockposition.getX()) + 0.5D, (double) blockposition.getY(), (double) ((float) blockposition.getZ()) + 0.5D, this.b); ++ } ++ } else { ++ this.h = true; ++ --this.d; ++ } ++ ++ } ++ ++ public boolean k() { ++ return this.d % 40 == 0; ++ } ++ ++ protected boolean l() { ++ return this.h; ++ } ++ ++ protected boolean m() { ++ int i = this.i; ++ int j = this.j; ++ BlockPosition blockposition = this.a.getChunkCoordinates(); ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ ++ for (int k = this.f; k <= j; k = k > 0 ? -k : 1 - k) { ++ for (int l = 0; l < i; ++l) { ++ for (int i1 = 0; i1 <= l; i1 = i1 > 0 ? -i1 : 1 - i1) { ++ for (int j1 = i1 < l && i1 > -l ? l : 0; j1 <= l; j1 = j1 > 0 ? -j1 : 1 - j1) { ++ blockposition_mutableblockposition.a((BaseBlockPosition) blockposition, i1, k - 1, j1); ++ if (this.a.a((BlockPosition) blockposition_mutableblockposition) && this.a(this.a.world, blockposition_mutableblockposition)) { ++ this.e = blockposition_mutableblockposition; ++ return true; ++ } ++ } ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ protected abstract boolean a(IWorldReader iworldreader, BlockPosition blockposition); ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c9aaa63fcb0abe5628798827003c677c883c2a18 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java +@@ -0,0 +1,127 @@ ++package net.minecraft.world.entity.ai.goal; ++ ++import com.google.common.collect.Sets; ++import java.util.EnumMap; ++import java.util.EnumSet; ++import java.util.Map; ++import java.util.Set; ++import java.util.function.Supplier; ++import java.util.stream.Stream; ++import net.minecraft.util.profiling.GameProfilerFiller; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class PathfinderGoalSelector { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private static final PathfinderGoalWrapped b = new PathfinderGoalWrapped(Integer.MAX_VALUE, new PathfinderGoal() { ++ @Override ++ public boolean a() { ++ return false; ++ } ++ }) { ++ @Override ++ public boolean g() { ++ return false; ++ } ++ }; ++ private final Map c = new EnumMap(PathfinderGoal.Type.class); ++ private final Set d = Sets.newLinkedHashSet(); ++ private final Supplier e; ++ private final EnumSet f = EnumSet.noneOf(PathfinderGoal.Type.class); ++ private int g = 3; ++ ++ public PathfinderGoalSelector(Supplier supplier) { ++ this.e = supplier; ++ } ++ ++ public void a(int i, PathfinderGoal pathfindergoal) { ++ this.d.add(new PathfinderGoalWrapped(i, pathfindergoal)); ++ } ++ ++ public void a(PathfinderGoal pathfindergoal) { ++ this.d.stream().filter((pathfindergoalwrapped) -> { ++ return pathfindergoalwrapped.j() == pathfindergoal; ++ }).filter(PathfinderGoalWrapped::g).forEach(PathfinderGoalWrapped::d); ++ this.d.removeIf((pathfindergoalwrapped) -> { ++ return pathfindergoalwrapped.j() == pathfindergoal; ++ }); ++ } ++ ++ public void doTick() { ++ GameProfilerFiller gameprofilerfiller = (GameProfilerFiller) this.e.get(); ++ ++ gameprofilerfiller.enter("goalCleanup"); ++ this.d().filter((pathfindergoalwrapped) -> { ++ boolean flag; ++ ++ if (pathfindergoalwrapped.g()) { ++ Stream stream = pathfindergoalwrapped.i().stream(); ++ EnumSet enumset = this.f; ++ ++ this.f.getClass(); ++ if (!stream.anyMatch(enumset::contains) && pathfindergoalwrapped.b()) { ++ flag = false; ++ return flag; ++ } ++ } ++ ++ flag = true; ++ return flag; ++ }).forEach(PathfinderGoal::d); ++ this.c.forEach((pathfindergoal_type, pathfindergoalwrapped) -> { ++ if (!pathfindergoalwrapped.g()) { ++ this.c.remove(pathfindergoal_type); ++ } ++ ++ }); ++ gameprofilerfiller.exit(); ++ gameprofilerfiller.enter("goalUpdate"); ++ this.d.stream().filter((pathfindergoalwrapped) -> { ++ return !pathfindergoalwrapped.g(); ++ }).filter((pathfindergoalwrapped) -> { ++ Stream stream = pathfindergoalwrapped.i().stream(); ++ EnumSet enumset = this.f; ++ ++ this.f.getClass(); ++ return stream.noneMatch(enumset::contains); ++ }).filter((pathfindergoalwrapped) -> { ++ return pathfindergoalwrapped.i().stream().allMatch((pathfindergoal_type) -> { ++ return ((PathfinderGoalWrapped) this.c.getOrDefault(pathfindergoal_type, PathfinderGoalSelector.b)).a(pathfindergoalwrapped); ++ }); ++ }).filter(PathfinderGoalWrapped::a).forEach((pathfindergoalwrapped) -> { ++ pathfindergoalwrapped.i().forEach((pathfindergoal_type) -> { ++ PathfinderGoalWrapped pathfindergoalwrapped1 = (PathfinderGoalWrapped) this.c.getOrDefault(pathfindergoal_type, PathfinderGoalSelector.b); ++ ++ pathfindergoalwrapped1.d(); ++ this.c.put(pathfindergoal_type, pathfindergoalwrapped); ++ }); ++ pathfindergoalwrapped.c(); ++ }); ++ gameprofilerfiller.exit(); ++ gameprofilerfiller.enter("goalTick"); ++ this.d().forEach(PathfinderGoalWrapped::e); ++ gameprofilerfiller.exit(); ++ } ++ ++ public Stream d() { ++ return this.d.stream().filter(PathfinderGoalWrapped::g); ++ } ++ ++ public void a(PathfinderGoal.Type pathfindergoal_type) { ++ this.f.add(pathfindergoal_type); ++ } ++ ++ public void b(PathfinderGoal.Type pathfindergoal_type) { ++ this.f.remove(pathfindergoal_type); ++ } ++ ++ public void a(PathfinderGoal.Type pathfindergoal_type, boolean flag) { ++ if (flag) { ++ this.b(pathfindergoal_type); ++ } else { ++ this.a(pathfindergoal_type); ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7395335ee97237376d34e315ea1d7d46766b278a +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java +@@ -0,0 +1,86 @@ ++package net.minecraft.world.entity.ai.goal; ++ ++import java.util.EnumSet; ++import javax.annotation.Nullable; ++ ++public class PathfinderGoalWrapped extends PathfinderGoal { ++ ++ private final PathfinderGoal a; ++ private final int b; ++ private boolean c; ++ ++ public PathfinderGoalWrapped(int i, PathfinderGoal pathfindergoal) { ++ this.b = i; ++ this.a = pathfindergoal; ++ } ++ ++ public boolean a(PathfinderGoalWrapped pathfindergoalwrapped) { ++ return this.C_() && pathfindergoalwrapped.h() < this.h(); ++ } ++ ++ @Override ++ public boolean a() { ++ return this.a.a(); ++ } ++ ++ @Override ++ public boolean b() { ++ return this.a.b(); ++ } ++ ++ @Override ++ public boolean C_() { ++ return this.a.C_(); ++ } ++ ++ @Override ++ public void c() { ++ if (!this.c) { ++ this.c = true; ++ this.a.c(); ++ } ++ } ++ ++ @Override ++ public void d() { ++ if (this.c) { ++ this.c = false; ++ this.a.d(); ++ } ++ } ++ ++ @Override ++ public void e() { ++ this.a.e(); ++ } ++ ++ @Override ++ public void a(EnumSet enumset) { ++ this.a.a(enumset); ++ } ++ ++ @Override ++ public EnumSet i() { ++ return this.a.i(); ++ } ++ ++ public boolean g() { ++ return this.c; ++ } ++ ++ public int h() { ++ return this.b; ++ } ++ ++ public PathfinderGoal j() { ++ return this.a; ++ } ++ ++ public boolean equals(@Nullable Object object) { ++ return this == object ? true : (object != null && this.getClass() == object.getClass() ? this.a.equals(((PathfinderGoalWrapped) object).a) : false); ++ } ++ ++ public int hashCode() { ++ return this.a.hashCode(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java b/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a7f5e4a499c1f6fb1450e536dbf117a8af3b3b84 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java +@@ -0,0 +1,233 @@ ++package net.minecraft.world.entity.ai.gossip; ++ ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Maps; ++import com.google.common.collect.Sets; ++import com.mojang.serialization.DataResult; ++import com.mojang.serialization.Dynamic; ++import com.mojang.serialization.DynamicOps; ++import it.unimi.dsi.fastutil.objects.Object2IntMap; ++import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; ++import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectIterator; ++import java.util.Arrays; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Random; ++import java.util.Set; ++import java.util.UUID; ++import java.util.function.Predicate; ++import java.util.stream.Collectors; ++import java.util.stream.Stream; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.MinecraftSerializableUUID; ++ ++public class Reputation { ++ ++ private final Map a = Maps.newHashMap(); ++ ++ public Reputation() {} ++ ++ public void b() { ++ Iterator iterator = this.a.values().iterator(); ++ ++ while (iterator.hasNext()) { ++ Reputation.a reputation_a = (Reputation.a) iterator.next(); ++ ++ reputation_a.a(); ++ if (reputation_a.b()) { ++ iterator.remove(); ++ } ++ } ++ ++ } ++ ++ private Stream c() { ++ return this.a.entrySet().stream().flatMap((entry) -> { ++ return ((Reputation.a) entry.getValue()).a((UUID) entry.getKey()); ++ }); ++ } ++ ++ private Collection a(Random random, int i) { ++ List list = (List) this.c().collect(Collectors.toList()); ++ ++ if (list.isEmpty()) { ++ return Collections.emptyList(); ++ } else { ++ int[] aint = new int[list.size()]; ++ int j = 0; ++ ++ for (int k = 0; k < list.size(); ++k) { ++ Reputation.b reputation_b = (Reputation.b) list.get(k); ++ ++ j += Math.abs(reputation_b.a()); ++ aint[k] = j - 1; ++ } ++ ++ Set set = Sets.newIdentityHashSet(); ++ ++ for (int l = 0; l < i; ++l) { ++ int i1 = random.nextInt(j); ++ int j1 = Arrays.binarySearch(aint, i1); ++ ++ set.add(list.get(j1 < 0 ? -j1 - 1 : j1)); ++ } ++ ++ return set; ++ } ++ } ++ ++ private Reputation.a a(UUID uuid) { ++ return (Reputation.a) this.a.computeIfAbsent(uuid, (uuid1) -> { ++ return new Reputation.a(); ++ }); ++ } ++ ++ public void a(Reputation reputation, Random random, int i) { ++ Collection collection = reputation.a(random, i); ++ ++ collection.forEach((reputation_b) -> { ++ int j = reputation_b.c - reputation_b.b.j; ++ ++ if (j >= 2) { ++ this.a(reputation_b.a).a.mergeInt(reputation_b.b, j, Reputation::a); ++ } ++ ++ }); ++ } ++ ++ public int a(UUID uuid, Predicate predicate) { ++ Reputation.a reputation_a = (Reputation.a) this.a.get(uuid); ++ ++ return reputation_a != null ? reputation_a.a(predicate) : 0; ++ } ++ ++ public void a(UUID uuid, ReputationType reputationtype, int i) { ++ Reputation.a reputation_a = this.a(uuid); ++ ++ reputation_a.a.mergeInt(reputationtype, i, (integer, integer1) -> { ++ return this.a(reputationtype, integer, integer1); ++ }); ++ reputation_a.a(reputationtype); ++ if (reputation_a.b()) { ++ this.a.remove(uuid); ++ } ++ ++ } ++ ++ public Dynamic a(DynamicOps dynamicops) { ++ return new Dynamic(dynamicops, dynamicops.createList(this.c().map((reputation_b) -> { ++ return reputation_b.a(dynamicops); ++ }).map(Dynamic::getValue))); ++ } ++ ++ public void a(Dynamic dynamic) { ++ dynamic.asStream().map(Reputation.b::a).flatMap((dataresult) -> { ++ return SystemUtils.a(dataresult.result()); ++ }).forEach((reputation_b) -> { ++ this.a(reputation_b.a).a.put(reputation_b.b, reputation_b.c); ++ }); ++ } ++ ++ private static int a(int i, int j) { ++ return Math.max(i, j); ++ } ++ ++ private int a(ReputationType reputationtype, int i, int j) { ++ int k = i + j; ++ ++ return k > reputationtype.h ? Math.max(reputationtype.h, i) : k; ++ } ++ ++ static class a { ++ ++ private final Object2IntMap a; ++ ++ private a() { ++ this.a = new Object2IntOpenHashMap(); ++ } ++ ++ public int a(Predicate predicate) { ++ return this.a.object2IntEntrySet().stream().filter((entry) -> { ++ return predicate.test(entry.getKey()); ++ }).mapToInt((entry) -> { ++ return entry.getIntValue() * ((ReputationType) entry.getKey()).g; ++ }).sum(); ++ } ++ ++ public Stream a(UUID uuid) { ++ return this.a.object2IntEntrySet().stream().map((entry) -> { ++ return new Reputation.b(uuid, (ReputationType) entry.getKey(), entry.getIntValue()); ++ }); ++ } ++ ++ public void a() { ++ ObjectIterator objectiterator = this.a.object2IntEntrySet().iterator(); ++ ++ while (objectiterator.hasNext()) { ++ Entry entry = (Entry) objectiterator.next(); ++ int i = entry.getIntValue() - ((ReputationType) entry.getKey()).i; ++ ++ if (i < 2) { ++ objectiterator.remove(); ++ } else { ++ entry.setValue(i); ++ } ++ } ++ ++ } ++ ++ public boolean b() { ++ return this.a.isEmpty(); ++ } ++ ++ public void a(ReputationType reputationtype) { ++ int i = this.a.getInt(reputationtype); ++ ++ if (i > reputationtype.h) { ++ this.a.put(reputationtype, reputationtype.h); ++ } ++ ++ if (i < 2) { ++ this.b(reputationtype); ++ } ++ ++ } ++ ++ public void b(ReputationType reputationtype) { ++ this.a.removeInt(reputationtype); ++ } ++ } ++ ++ static class b { ++ ++ public final UUID a; ++ public final ReputationType b; ++ public final int c; ++ ++ public b(UUID uuid, ReputationType reputationtype, int i) { ++ this.a = uuid; ++ this.b = reputationtype; ++ this.c = i; ++ } ++ ++ public int a() { ++ return this.c * this.b.g; ++ } ++ ++ public String toString() { ++ return "GossipEntry{target=" + this.a + ", type=" + this.b + ", value=" + this.c + '}'; ++ } ++ ++ public Dynamic a(DynamicOps dynamicops) { ++ return new Dynamic(dynamicops, dynamicops.createMap(ImmutableMap.of(dynamicops.createString("Target"), MinecraftSerializableUUID.a.encodeStart(dynamicops, this.a).result().orElseThrow(RuntimeException::new), dynamicops.createString("Type"), dynamicops.createString(this.b.f), dynamicops.createString("Value"), dynamicops.createInt(this.c)))); ++ } ++ ++ public static DataResult a(Dynamic dynamic) { ++ return DataResult.unbox(DataResult.instance().group(dynamic.get("Target").read(MinecraftSerializableUUID.a), dynamic.get("Type").asString().map(ReputationType::a), dynamic.get("Value").asNumber().map(Number::intValue)).apply(DataResult.instance(), Reputation.b::new)); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/ReputationType.java b/src/main/java/net/minecraft/world/entity/ai/gossip/ReputationType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..89651e6e3bb1cfbb8eb8a120b3c3e553cd831a68 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/ReputationType.java +@@ -0,0 +1,34 @@ ++package net.minecraft.world.entity.ai.gossip; ++ ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++import java.util.function.Function; ++import java.util.stream.Stream; ++import javax.annotation.Nullable; ++ ++public enum ReputationType { ++ ++ MAJOR_NEGATIVE("major_negative", -5, 100, 10, 10), MINOR_NEGATIVE("minor_negative", -1, 200, 20, 20), MINOR_POSITIVE("minor_positive", 1, 200, 1, 5), MAJOR_POSITIVE("major_positive", 5, 100, 0, 100), TRADING("trading", 1, 25, 2, 20); ++ ++ public final String f; ++ public final int g; ++ public final int h; ++ public final int i; ++ public final int j; ++ private static final Map k = (Map) Stream.of(values()).collect(ImmutableMap.toImmutableMap((reputationtype) -> { ++ return reputationtype.f; ++ }, Function.identity())); ++ ++ private ReputationType(String s, int i, int j, int k, int l) { ++ this.f = s; ++ this.g = i; ++ this.h = j; ++ this.i = k; ++ this.j = l; ++ } ++ ++ @Nullable ++ public static ReputationType a(String s) { ++ return (ReputationType) ReputationType.k.get(s); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/Navigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/Navigation.java +new file mode 100644 +index 0000000000000000000000000000000000000000..942e03578836524ba746bc37699677eb06cc7803 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/Navigation.java +@@ -0,0 +1,260 @@ ++package net.minecraft.world.entity.ai.navigation; ++ ++import java.util.Iterator; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.pathfinder.PathEntity; ++import net.minecraft.world.level.pathfinder.PathMode; ++import net.minecraft.world.level.pathfinder.PathPoint; ++import net.minecraft.world.level.pathfinder.PathType; ++import net.minecraft.world.level.pathfinder.Pathfinder; ++import net.minecraft.world.level.pathfinder.PathfinderNormal; ++import net.minecraft.world.phys.Vec3D; ++ ++public class Navigation extends NavigationAbstract { ++ ++ private boolean p; ++ ++ public Navigation(EntityInsentient entityinsentient, World world) { ++ super(entityinsentient, world); ++ } ++ ++ @Override ++ protected Pathfinder a(int i) { ++ this.o = new PathfinderNormal(); ++ this.o.a(true); ++ return new Pathfinder(this.o, i); ++ } ++ ++ @Override ++ protected boolean a() { ++ return this.a.isOnGround() || this.p() || this.a.isPassenger(); ++ } ++ ++ @Override ++ protected Vec3D b() { ++ return new Vec3D(this.a.locX(), (double) this.u(), this.a.locZ()); ++ } ++ ++ @Override ++ public PathEntity a(BlockPosition blockposition, int i) { ++ BlockPosition blockposition1; ++ ++ if (this.b.getType(blockposition).isAir()) { ++ for (blockposition1 = blockposition.down(); blockposition1.getY() > 0 && this.b.getType(blockposition1).isAir(); blockposition1 = blockposition1.down()) { ++ ; ++ } ++ ++ if (blockposition1.getY() > 0) { ++ return super.a(blockposition1.up(), i); ++ } ++ ++ while (blockposition1.getY() < this.b.getBuildHeight() && this.b.getType(blockposition1).isAir()) { ++ blockposition1 = blockposition1.up(); ++ } ++ ++ blockposition = blockposition1; ++ } ++ ++ if (!this.b.getType(blockposition).getMaterial().isBuildable()) { ++ return super.a(blockposition, i); ++ } else { ++ for (blockposition1 = blockposition.up(); blockposition1.getY() < this.b.getBuildHeight() && this.b.getType(blockposition1).getMaterial().isBuildable(); blockposition1 = blockposition1.up()) { ++ ; ++ } ++ ++ return super.a(blockposition1, i); ++ } ++ } ++ ++ @Override ++ public PathEntity a(Entity entity, int i) { ++ return this.a(entity.getChunkCoordinates(), i); ++ } ++ ++ private int u() { ++ if (this.a.isInWater() && this.r()) { ++ int i = MathHelper.floor(this.a.locY()); ++ Block block = this.b.getType(new BlockPosition(this.a.locX(), (double) i, this.a.locZ())).getBlock(); ++ int j = 0; ++ ++ do { ++ if (block != Blocks.WATER) { ++ return i; ++ } ++ ++ ++i; ++ block = this.b.getType(new BlockPosition(this.a.locX(), (double) i, this.a.locZ())).getBlock(); ++ ++j; ++ } while (j <= 16); ++ ++ return MathHelper.floor(this.a.locY()); ++ } else { ++ return MathHelper.floor(this.a.locY() + 0.5D); ++ } ++ } ++ ++ @Override ++ protected void D_() { ++ super.D_(); ++ if (this.p) { ++ if (this.b.e(new BlockPosition(this.a.locX(), this.a.locY() + 0.5D, this.a.locZ()))) { ++ return; ++ } ++ ++ for (int i = 0; i < this.c.e(); ++i) { ++ PathPoint pathpoint = this.c.a(i); ++ ++ if (this.b.e(new BlockPosition(pathpoint.a, pathpoint.b, pathpoint.c))) { ++ this.c.b(i); ++ return; ++ } ++ } ++ } ++ ++ } ++ ++ @Override ++ protected boolean a(Vec3D vec3d, Vec3D vec3d1, int i, int j, int k) { ++ int l = MathHelper.floor(vec3d.x); ++ int i1 = MathHelper.floor(vec3d.z); ++ double d0 = vec3d1.x - vec3d.x; ++ double d1 = vec3d1.z - vec3d.z; ++ double d2 = d0 * d0 + d1 * d1; ++ ++ if (d2 < 1.0E-8D) { ++ return false; ++ } else { ++ double d3 = 1.0D / Math.sqrt(d2); ++ ++ d0 *= d3; ++ d1 *= d3; ++ i += 2; ++ k += 2; ++ if (!this.a(l, MathHelper.floor(vec3d.y), i1, i, j, k, vec3d, d0, d1)) { ++ return false; ++ } else { ++ i -= 2; ++ k -= 2; ++ double d4 = 1.0D / Math.abs(d0); ++ double d5 = 1.0D / Math.abs(d1); ++ double d6 = (double) l - vec3d.x; ++ double d7 = (double) i1 - vec3d.z; ++ ++ if (d0 >= 0.0D) { ++ ++d6; ++ } ++ ++ if (d1 >= 0.0D) { ++ ++d7; ++ } ++ ++ d6 /= d0; ++ d7 /= d1; ++ int j1 = d0 < 0.0D ? -1 : 1; ++ int k1 = d1 < 0.0D ? -1 : 1; ++ int l1 = MathHelper.floor(vec3d1.x); ++ int i2 = MathHelper.floor(vec3d1.z); ++ int j2 = l1 - l; ++ int k2 = i2 - i1; ++ ++ do { ++ if (j2 * j1 <= 0 && k2 * k1 <= 0) { ++ return true; ++ } ++ ++ if (d6 < d7) { ++ d6 += d4; ++ l += j1; ++ j2 = l1 - l; ++ } else { ++ d7 += d5; ++ i1 += k1; ++ k2 = i2 - i1; ++ } ++ } while (this.a(l, MathHelper.floor(vec3d.y), i1, i, j, k, vec3d, d0, d1)); ++ ++ return false; ++ } ++ } ++ } ++ ++ private boolean a(int i, int j, int k, int l, int i1, int j1, Vec3D vec3d, double d0, double d1) { ++ int k1 = i - l / 2; ++ int l1 = k - j1 / 2; ++ ++ if (!this.b(k1, j, l1, l, i1, j1, vec3d, d0, d1)) { ++ return false; ++ } else { ++ for (int i2 = k1; i2 < k1 + l; ++i2) { ++ for (int j2 = l1; j2 < l1 + j1; ++j2) { ++ double d2 = (double) i2 + 0.5D - vec3d.x; ++ double d3 = (double) j2 + 0.5D - vec3d.z; ++ ++ if (d2 * d0 + d3 * d1 >= 0.0D) { ++ PathType pathtype = this.o.a(this.b, i2, j - 1, j2, this.a, l, i1, j1, true, true); ++ ++ if (!this.a(pathtype)) { ++ return false; ++ } ++ ++ pathtype = this.o.a(this.b, i2, j, j2, this.a, l, i1, j1, true, true); ++ float f = this.a.a(pathtype); ++ ++ if (f < 0.0F || f >= 8.0F) { ++ return false; ++ } ++ ++ if (pathtype == PathType.DAMAGE_FIRE || pathtype == PathType.DANGER_FIRE || pathtype == PathType.DAMAGE_OTHER) { ++ return false; ++ } ++ } ++ } ++ } ++ ++ return true; ++ } ++ } ++ ++ protected boolean a(PathType pathtype) { ++ return pathtype == PathType.WATER ? false : (pathtype == PathType.LAVA ? false : pathtype != PathType.OPEN); ++ } ++ ++ private boolean b(int i, int j, int k, int l, int i1, int j1, Vec3D vec3d, double d0, double d1) { ++ Iterator iterator = BlockPosition.a(new BlockPosition(i, j, k), new BlockPosition(i + l - 1, j + i1 - 1, k + j1 - 1)).iterator(); ++ ++ BlockPosition blockposition; ++ double d2; ++ double d3; ++ ++ do { ++ if (!iterator.hasNext()) { ++ return true; ++ } ++ ++ blockposition = (BlockPosition) iterator.next(); ++ d2 = (double) blockposition.getX() + 0.5D - vec3d.x; ++ d3 = (double) blockposition.getZ() + 0.5D - vec3d.z; ++ } while (d2 * d0 + d3 * d1 < 0.0D || this.b.getType(blockposition).a((IBlockAccess) this.b, blockposition, PathMode.LAND)); ++ ++ return false; ++ } ++ ++ public void a(boolean flag) { ++ this.o.b(flag); ++ } ++ ++ public boolean f() { ++ return this.o.c(); ++ } ++ ++ public void c(boolean flag) { ++ this.p = flag; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8848a7552a0ef3944560a71f71620c6bd0f08c10 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +@@ -0,0 +1,381 @@ ++package net.minecraft.world.entity.ai.navigation; ++ ++import com.google.common.collect.ImmutableSet; ++import java.util.Set; ++import java.util.stream.Collectors; ++import java.util.stream.Stream; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BaseBlockPosition; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.IPosition; ++import net.minecraft.network.protocol.game.PacketDebug; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; ++import net.minecraft.world.level.ChunkCache; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.pathfinder.PathEntity; ++import net.minecraft.world.level.pathfinder.PathPoint; ++import net.minecraft.world.level.pathfinder.Pathfinder; ++import net.minecraft.world.level.pathfinder.PathfinderAbstract; ++import net.minecraft.world.level.pathfinder.PathfinderNormal; ++import net.minecraft.world.phys.Vec3D; ++ ++public abstract class NavigationAbstract { ++ ++ protected final EntityInsentient a; ++ protected final World b; ++ @Nullable ++ protected PathEntity c; ++ protected double d; ++ protected int e; ++ protected int f; ++ protected Vec3D g; ++ protected BaseBlockPosition h; ++ protected long i; ++ protected long j; ++ protected double k; ++ protected float l; ++ protected boolean m; ++ protected long n; ++ protected PathfinderAbstract o; ++ private BlockPosition p; ++ private int q; ++ private float r; ++ private final Pathfinder s; ++ private boolean t; ++ ++ public NavigationAbstract(EntityInsentient entityinsentient, World world) { ++ this.g = Vec3D.ORIGIN; ++ this.h = BaseBlockPosition.ZERO; ++ this.l = 0.5F; ++ this.r = 1.0F; ++ this.a = entityinsentient; ++ this.b = world; ++ int i = MathHelper.floor(entityinsentient.b(GenericAttributes.FOLLOW_RANGE) * 16.0D); ++ ++ this.s = this.a(i); ++ } ++ ++ public void g() { ++ this.r = 1.0F; ++ } ++ ++ public void a(float f) { ++ this.r = f; ++ } ++ ++ public BlockPosition h() { ++ return this.p; ++ } ++ ++ protected abstract Pathfinder a(int i); ++ ++ public void a(double d0) { ++ this.d = d0; ++ } ++ ++ public boolean i() { ++ return this.m; ++ } ++ ++ public void j() { ++ if (this.b.getTime() - this.n > 20L) { ++ if (this.p != null) { ++ this.c = null; ++ this.c = this.a(this.p, this.q); ++ this.n = this.b.getTime(); ++ this.m = false; ++ } ++ } else { ++ this.m = true; ++ } ++ ++ } ++ ++ @Nullable ++ public final PathEntity a(double d0, double d1, double d2, int i) { ++ return this.a(new BlockPosition(d0, d1, d2), i); ++ } ++ ++ @Nullable ++ public PathEntity a(Stream stream, int i) { ++ return this.a((Set) stream.collect(Collectors.toSet()), 8, false, i); ++ } ++ ++ @Nullable ++ public PathEntity a(Set set, int i) { ++ return this.a(set, 8, false, i); ++ } ++ ++ @Nullable ++ public PathEntity a(BlockPosition blockposition, int i) { ++ return this.a(ImmutableSet.of(blockposition), 8, false, i); ++ } ++ ++ @Nullable ++ public PathEntity a(Entity entity, int i) { ++ return this.a(ImmutableSet.of(entity.getChunkCoordinates()), 16, true, i); ++ } ++ ++ @Nullable ++ protected PathEntity a(Set set, int i, boolean flag, int j) { ++ if (set.isEmpty()) { ++ return null; ++ } else if (this.a.locY() < 0.0D) { ++ return null; ++ } else if (!this.a()) { ++ return null; ++ } else if (this.c != null && !this.c.c() && set.contains(this.p)) { ++ return this.c; ++ } else { ++ this.b.getMethodProfiler().enter("pathfind"); ++ float f = (float) this.a.b(GenericAttributes.FOLLOW_RANGE); ++ BlockPosition blockposition = flag ? this.a.getChunkCoordinates().up() : this.a.getChunkCoordinates(); ++ int k = (int) (f + (float) i); ++ ChunkCache chunkcache = new ChunkCache(this.b, blockposition.b(-k, -k, -k), blockposition.b(k, k, k)); ++ PathEntity pathentity = this.s.a(chunkcache, this.a, set, f, j, this.r); ++ ++ this.b.getMethodProfiler().exit(); ++ if (pathentity != null && pathentity.m() != null) { ++ this.p = pathentity.m(); ++ this.q = j; ++ this.f(); ++ } ++ ++ return pathentity; ++ } ++ } ++ ++ public boolean a(double d0, double d1, double d2, double d3) { ++ return this.a(this.a(d0, d1, d2, 1), d3); ++ } ++ ++ public boolean a(Entity entity, double d0) { ++ PathEntity pathentity = this.a(entity, 1); ++ ++ return pathentity != null && this.a(pathentity, d0); ++ } ++ ++ public boolean a(@Nullable PathEntity pathentity, double d0) { ++ if (pathentity == null) { ++ this.c = null; ++ return false; ++ } else { ++ if (!pathentity.a(this.c)) { ++ this.c = pathentity; ++ } ++ ++ if (this.m()) { ++ return false; ++ } else { ++ this.D_(); ++ if (this.c.e() <= 0) { ++ return false; ++ } else { ++ this.d = d0; ++ Vec3D vec3d = this.b(); ++ ++ this.f = this.e; ++ this.g = vec3d; ++ return true; ++ } ++ } ++ } ++ } ++ ++ @Nullable ++ public PathEntity k() { ++ return this.c; ++ } ++ ++ public void c() { ++ ++this.e; ++ if (this.m) { ++ this.j(); ++ } ++ ++ if (!this.m()) { ++ Vec3D vec3d; ++ ++ if (this.a()) { ++ this.l(); ++ } else if (this.c != null && !this.c.c()) { ++ vec3d = this.b(); ++ Vec3D vec3d1 = this.c.a((Entity) this.a); ++ ++ if (vec3d.y > vec3d1.y && !this.a.isOnGround() && MathHelper.floor(vec3d.x) == MathHelper.floor(vec3d1.x) && MathHelper.floor(vec3d.z) == MathHelper.floor(vec3d1.z)) { ++ this.c.a(); ++ } ++ } ++ ++ PacketDebug.a(this.b, this.a, this.c, this.l); ++ if (!this.m()) { ++ vec3d = this.c.a((Entity) this.a); ++ BlockPosition blockposition = new BlockPosition(vec3d); ++ ++ this.a.getControllerMove().a(vec3d.x, this.b.getType(blockposition.down()).isAir() ? vec3d.y : PathfinderNormal.a((IBlockAccess) this.b, blockposition), vec3d.z, this.d); ++ } ++ } ++ } ++ ++ protected void l() { ++ Vec3D vec3d = this.b(); ++ ++ this.l = this.a.getWidth() > 0.75F ? this.a.getWidth() / 2.0F : 0.75F - this.a.getWidth() / 2.0F; ++ BlockPosition blockposition = this.c.g(); ++ double d0 = Math.abs(this.a.locX() - ((double) blockposition.getX() + 0.5D)); ++ double d1 = Math.abs(this.a.locY() - (double) blockposition.getY()); ++ double d2 = Math.abs(this.a.locZ() - ((double) blockposition.getZ() + 0.5D)); ++ boolean flag = d0 < (double) this.l && d2 < (double) this.l && d1 < 1.0D; ++ ++ if (flag || this.a.b(this.c.h().l) && this.b(vec3d)) { ++ this.c.a(); ++ } ++ ++ this.a(vec3d); ++ } ++ ++ private boolean b(Vec3D vec3d) { ++ if (this.c.f() + 1 >= this.c.e()) { ++ return false; ++ } else { ++ Vec3D vec3d1 = Vec3D.c((BaseBlockPosition) this.c.g()); ++ ++ if (!vec3d.a((IPosition) vec3d1, 2.0D)) { ++ return false; ++ } else { ++ Vec3D vec3d2 = Vec3D.c((BaseBlockPosition) this.c.d(this.c.f() + 1)); ++ Vec3D vec3d3 = vec3d2.d(vec3d1); ++ Vec3D vec3d4 = vec3d.d(vec3d1); ++ ++ return vec3d3.b(vec3d4) > 0.0D; ++ } ++ } ++ } ++ ++ protected void a(Vec3D vec3d) { ++ if (this.e - this.f > 100) { ++ if (vec3d.distanceSquared(this.g) < 2.25D) { ++ this.t = true; ++ this.o(); ++ } else { ++ this.t = false; ++ } ++ ++ this.f = this.e; ++ this.g = vec3d; ++ } ++ ++ if (this.c != null && !this.c.c()) { ++ BlockPosition blockposition = this.c.g(); ++ ++ if (blockposition.equals(this.h)) { ++ this.i += SystemUtils.getMonotonicMillis() - this.j; ++ } else { ++ this.h = blockposition; ++ double d0 = vec3d.f(Vec3D.c(this.h)); ++ ++ this.k = this.a.dN() > 0.0F ? d0 / (double) this.a.dN() * 1000.0D : 0.0D; ++ } ++ ++ if (this.k > 0.0D && (double) this.i > this.k * 3.0D) { ++ this.e(); ++ } ++ ++ this.j = SystemUtils.getMonotonicMillis(); ++ } ++ ++ } ++ ++ private void e() { ++ this.f(); ++ this.o(); ++ } ++ ++ private void f() { ++ this.h = BaseBlockPosition.ZERO; ++ this.i = 0L; ++ this.k = 0.0D; ++ this.t = false; ++ } ++ ++ public boolean m() { ++ return this.c == null || this.c.c(); ++ } ++ ++ public boolean n() { ++ return !this.m(); ++ } ++ ++ public void o() { ++ this.c = null; ++ } ++ ++ protected abstract Vec3D b(); ++ ++ protected abstract boolean a(); ++ ++ protected boolean p() { ++ return this.a.aH() || this.a.aQ(); ++ } ++ ++ protected void D_() { ++ if (this.c != null) { ++ for (int i = 0; i < this.c.e(); ++i) { ++ PathPoint pathpoint = this.c.a(i); ++ PathPoint pathpoint1 = i + 1 < this.c.e() ? this.c.a(i + 1) : null; ++ IBlockData iblockdata = this.b.getType(new BlockPosition(pathpoint.a, pathpoint.b, pathpoint.c)); ++ ++ if (iblockdata.a(Blocks.CAULDRON)) { ++ this.c.a(i, pathpoint.a(pathpoint.a, pathpoint.b + 1, pathpoint.c)); ++ if (pathpoint1 != null && pathpoint.b >= pathpoint1.b) { ++ this.c.a(i + 1, pathpoint.a(pathpoint1.a, pathpoint.b + 1, pathpoint1.c)); ++ } ++ } ++ } ++ ++ } ++ } ++ ++ protected abstract boolean a(Vec3D vec3d, Vec3D vec3d1, int i, int j, int k); ++ ++ public boolean a(BlockPosition blockposition) { ++ BlockPosition blockposition1 = blockposition.down(); ++ ++ return this.b.getType(blockposition1).i(this.b, blockposition1); ++ } ++ ++ public PathfinderAbstract q() { ++ return this.o; ++ } ++ ++ public void d(boolean flag) { ++ this.o.c(flag); ++ } ++ ++ public boolean r() { ++ return this.o.e(); ++ } ++ ++ public void b(BlockPosition blockposition) { ++ if (this.c != null && !this.c.c() && this.c.e() != 0) { ++ PathPoint pathpoint = this.c.d(); ++ Vec3D vec3d = new Vec3D(((double) pathpoint.a + this.a.locX()) / 2.0D, ((double) pathpoint.b + this.a.locY()) / 2.0D, ((double) pathpoint.c + this.a.locZ()) / 2.0D); ++ ++ if (blockposition.a((IPosition) vec3d, (double) (this.c.e() - this.c.f()))) { ++ this.j(); ++ } ++ ++ } ++ } ++ ++ public boolean t() { ++ return this.t; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationFlying.java b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationFlying.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2e1efe7a048f64d494260d10a4ae5dba86af5e6c +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationFlying.java +@@ -0,0 +1,152 @@ ++package net.minecraft.world.entity.ai.navigation; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.network.protocol.game.PacketDebug; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.pathfinder.PathEntity; ++import net.minecraft.world.level.pathfinder.Pathfinder; ++import net.minecraft.world.level.pathfinder.PathfinderFlying; ++import net.minecraft.world.phys.Vec3D; ++ ++public class NavigationFlying extends NavigationAbstract { ++ ++ public NavigationFlying(EntityInsentient entityinsentient, World world) { ++ super(entityinsentient, world); ++ } ++ ++ @Override ++ protected Pathfinder a(int i) { ++ this.o = new PathfinderFlying(); ++ this.o.a(true); ++ return new Pathfinder(this.o, i); ++ } ++ ++ @Override ++ protected boolean a() { ++ return this.r() && this.p() || !this.a.isPassenger(); ++ } ++ ++ @Override ++ protected Vec3D b() { ++ return this.a.getPositionVector(); ++ } ++ ++ @Override ++ public PathEntity a(Entity entity, int i) { ++ return this.a(entity.getChunkCoordinates(), i); ++ } ++ ++ @Override ++ public void c() { ++ ++this.e; ++ if (this.m) { ++ this.j(); ++ } ++ ++ if (!this.m()) { ++ Vec3D vec3d; ++ ++ if (this.a()) { ++ this.l(); ++ } else if (this.c != null && !this.c.c()) { ++ vec3d = this.c.a((Entity) this.a); ++ if (MathHelper.floor(this.a.locX()) == MathHelper.floor(vec3d.x) && MathHelper.floor(this.a.locY()) == MathHelper.floor(vec3d.y) && MathHelper.floor(this.a.locZ()) == MathHelper.floor(vec3d.z)) { ++ this.c.a(); ++ } ++ } ++ ++ PacketDebug.a(this.b, this.a, this.c, this.l); ++ if (!this.m()) { ++ vec3d = this.c.a((Entity) this.a); ++ this.a.getControllerMove().a(vec3d.x, vec3d.y, vec3d.z, this.d); ++ } ++ } ++ } ++ ++ @Override ++ protected boolean a(Vec3D vec3d, Vec3D vec3d1, int i, int j, int k) { ++ int l = MathHelper.floor(vec3d.x); ++ int i1 = MathHelper.floor(vec3d.y); ++ int j1 = MathHelper.floor(vec3d.z); ++ double d0 = vec3d1.x - vec3d.x; ++ double d1 = vec3d1.y - vec3d.y; ++ double d2 = vec3d1.z - vec3d.z; ++ double d3 = d0 * d0 + d1 * d1 + d2 * d2; ++ ++ if (d3 < 1.0E-8D) { ++ return false; ++ } else { ++ double d4 = 1.0D / Math.sqrt(d3); ++ ++ d0 *= d4; ++ d1 *= d4; ++ d2 *= d4; ++ double d5 = 1.0D / Math.abs(d0); ++ double d6 = 1.0D / Math.abs(d1); ++ double d7 = 1.0D / Math.abs(d2); ++ double d8 = (double) l - vec3d.x; ++ double d9 = (double) i1 - vec3d.y; ++ double d10 = (double) j1 - vec3d.z; ++ ++ if (d0 >= 0.0D) { ++ ++d8; ++ } ++ ++ if (d1 >= 0.0D) { ++ ++d9; ++ } ++ ++ if (d2 >= 0.0D) { ++ ++d10; ++ } ++ ++ d8 /= d0; ++ d9 /= d1; ++ d10 /= d2; ++ int k1 = d0 < 0.0D ? -1 : 1; ++ int l1 = d1 < 0.0D ? -1 : 1; ++ int i2 = d2 < 0.0D ? -1 : 1; ++ int j2 = MathHelper.floor(vec3d1.x); ++ int k2 = MathHelper.floor(vec3d1.y); ++ int l2 = MathHelper.floor(vec3d1.z); ++ int i3 = j2 - l; ++ int j3 = k2 - i1; ++ int k3 = l2 - j1; ++ ++ while (i3 * k1 > 0 || j3 * l1 > 0 || k3 * i2 > 0) { ++ if (d8 < d10 && d8 <= d9) { ++ d8 += d5; ++ l += k1; ++ i3 = j2 - l; ++ } else if (d9 < d8 && d9 <= d10) { ++ d9 += d6; ++ i1 += l1; ++ j3 = k2 - i1; ++ } else { ++ d10 += d7; ++ j1 += i2; ++ k3 = l2 - j1; ++ } ++ } ++ ++ return true; ++ } ++ } ++ ++ public void a(boolean flag) { ++ this.o.b(flag); ++ } ++ ++ public void b(boolean flag) { ++ this.o.a(flag); ++ } ++ ++ @Override ++ public boolean a(BlockPosition blockposition) { ++ return this.b.getType(blockposition).a((IBlockAccess) this.b, blockposition, (Entity) this.a); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorGolemLastSeen.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorGolemLastSeen.java +new file mode 100644 +index 0000000000000000000000000000000000000000..41f1aecbf6b506231a1b3b525fe0ce23b35c7840 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorGolemLastSeen.java +@@ -0,0 +1,50 @@ ++package net.minecraft.world.entity.ai.sensing; ++ ++import com.google.common.collect.ImmutableSet; ++import java.util.List; ++import java.util.Optional; ++import java.util.Set; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++ ++public class SensorGolemLastSeen extends Sensor { ++ ++ public SensorGolemLastSeen() { ++ this(200); ++ } ++ ++ public SensorGolemLastSeen(int i) { ++ super(i); ++ } ++ ++ @Override ++ protected void a(WorldServer worldserver, EntityLiving entityliving) { ++ a(entityliving); ++ } ++ ++ @Override ++ public Set> a() { ++ return ImmutableSet.of(MemoryModuleType.MOBS); ++ } ++ ++ public static void a(EntityLiving entityliving) { ++ Optional> optional = entityliving.getBehaviorController().getMemory(MemoryModuleType.MOBS); ++ ++ if (optional.isPresent()) { ++ boolean flag = ((List) optional.get()).stream().anyMatch((entityliving1) -> { ++ return entityliving1.getEntityType().equals(EntityTypes.IRON_GOLEM); ++ }); ++ ++ if (flag) { ++ b(entityliving); ++ } ++ ++ } ++ } ++ ++ public static void b(EntityLiving entityliving) { ++ entityliving.getBehaviorController().a(MemoryModuleType.GOLEM_DETECTED_RECENTLY, true, 600L); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestBed.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestBed.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1e58a06860125243d0f7c062aca095dd2aae98f2 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestBed.java +@@ -0,0 +1,68 @@ ++package net.minecraft.world.entity.ai.sensing; ++ ++import com.google.common.collect.ImmutableSet; ++import it.unimi.dsi.fastutil.longs.Long2LongMap; ++import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; ++import java.util.Optional; ++import java.util.Set; ++import java.util.function.Predicate; ++import java.util.stream.Stream; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import net.minecraft.world.entity.ai.village.poi.VillagePlace; ++import net.minecraft.world.entity.ai.village.poi.VillagePlaceType; ++import net.minecraft.world.level.pathfinder.PathEntity; ++ ++public class SensorNearestBed extends Sensor { ++ ++ private final Long2LongMap a = new Long2LongOpenHashMap(); ++ private int b; ++ private long c; ++ ++ public SensorNearestBed() { ++ super(20); ++ } ++ ++ @Override ++ public Set> a() { ++ return ImmutableSet.of(MemoryModuleType.NEAREST_BED); ++ } ++ ++ protected void a(WorldServer worldserver, EntityInsentient entityinsentient) { ++ if (entityinsentient.isBaby()) { ++ this.b = 0; ++ this.c = worldserver.getTime() + (long) worldserver.getRandom().nextInt(20); ++ VillagePlace villageplace = worldserver.y(); ++ Predicate predicate = (blockposition) -> { ++ long i = blockposition.asLong(); ++ ++ if (this.a.containsKey(i)) { ++ return false; ++ } else if (++this.b >= 5) { ++ return false; ++ } else { ++ this.a.put(i, this.c + 40L); ++ return true; ++ } ++ }; ++ Stream stream = villageplace.a(VillagePlaceType.r.c(), predicate, entityinsentient.getChunkCoordinates(), 48, VillagePlace.Occupancy.ANY); ++ PathEntity pathentity = entityinsentient.getNavigation().a(stream, VillagePlaceType.r.d()); ++ ++ if (pathentity != null && pathentity.j()) { ++ BlockPosition blockposition = pathentity.m(); ++ Optional optional = villageplace.c(blockposition); ++ ++ if (optional.isPresent()) { ++ entityinsentient.getBehaviorController().setMemory(MemoryModuleType.NEAREST_BED, (Object) blockposition); ++ } ++ } else if (this.b < 5) { ++ this.a.long2LongEntrySet().removeIf((entry) -> { ++ return entry.getLongValue() < this.c; ++ }); ++ } ++ ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestItems.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestItems.java +new file mode 100644 +index 0000000000000000000000000000000000000000..418cd6d8b40d35aa3be73eb12f2e3b75597238b9 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestItems.java +@@ -0,0 +1,44 @@ ++package net.minecraft.world.entity.ai.sensing; ++ ++import com.google.common.collect.ImmutableSet; ++import java.util.Comparator; ++import java.util.List; ++import java.util.Optional; ++import java.util.Set; ++import java.util.stream.Stream; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.ai.BehaviorController; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import net.minecraft.world.entity.item.EntityItem; ++ ++public class SensorNearestItems extends Sensor { ++ ++ public SensorNearestItems() {} ++ ++ @Override ++ public Set> a() { ++ return ImmutableSet.of(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM); ++ } ++ ++ protected void a(WorldServer worldserver, EntityInsentient entityinsentient) { ++ BehaviorController behaviorcontroller = entityinsentient.getBehaviorController(); ++ List list = worldserver.a(EntityItem.class, entityinsentient.getBoundingBox().grow(8.0D, 4.0D, 8.0D), (entityitem) -> { ++ return true; ++ }); ++ ++ entityinsentient.getClass(); ++ list.sort(Comparator.comparingDouble(entityinsentient::h)); ++ Stream stream = list.stream().filter((entityitem) -> { ++ return entityinsentient.i(entityitem.getItemStack()); ++ }).filter((entityitem) -> { ++ return entityitem.a((Entity) entityinsentient, 9.0D); ++ }); ++ ++ entityinsentient.getClass(); ++ Optional optional = stream.filter(entityinsentient::hasLineOfSight).findFirst(); ++ ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, optional); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestLivingEntities.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestLivingEntities.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d3bb1c02d80fcd3586030b07f84e7ebdd97d873e +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestLivingEntities.java +@@ -0,0 +1,39 @@ ++package net.minecraft.world.entity.ai.sensing; ++ ++import com.google.common.collect.ImmutableSet; ++import java.util.Comparator; ++import java.util.List; ++import java.util.Set; ++import java.util.stream.Collectors; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.ai.BehaviorController; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import net.minecraft.world.phys.AxisAlignedBB; ++ ++public class SensorNearestLivingEntities extends Sensor { ++ ++ public SensorNearestLivingEntities() {} ++ ++ @Override ++ protected void a(WorldServer worldserver, EntityLiving entityliving) { ++ AxisAlignedBB axisalignedbb = entityliving.getBoundingBox().grow(16.0D, 16.0D, 16.0D); ++ List list = worldserver.a(EntityLiving.class, axisalignedbb, (entityliving1) -> { ++ return entityliving1 != entityliving && entityliving1.isAlive(); ++ }); ++ ++ entityliving.getClass(); ++ list.sort(Comparator.comparingDouble(entityliving::h)); ++ BehaviorController behaviorcontroller = entityliving.getBehaviorController(); ++ ++ behaviorcontroller.setMemory(MemoryModuleType.MOBS, (Object) list); ++ behaviorcontroller.setMemory(MemoryModuleType.VISIBLE_MOBS, list.stream().filter((entityliving1) -> { ++ return a(entityliving, entityliving1); ++ }).collect(Collectors.toList())); ++ } ++ ++ @Override ++ public Set> a() { ++ return ImmutableSet.of(MemoryModuleType.MOBS, MemoryModuleType.VISIBLE_MOBS); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestPlayers.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestPlayers.java +new file mode 100644 +index 0000000000000000000000000000000000000000..29abc7feec5358dce7d16958f0c5807f4bda992f +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestPlayers.java +@@ -0,0 +1,47 @@ ++package net.minecraft.world.entity.ai.sensing; ++ ++import com.google.common.collect.ImmutableSet; ++import java.util.Comparator; ++import java.util.List; ++import java.util.Optional; ++import java.util.Set; ++import java.util.stream.Collectors; ++import java.util.stream.Stream; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.IEntitySelector; ++import net.minecraft.world.entity.ai.BehaviorController; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import net.minecraft.world.entity.player.EntityHuman; ++ ++public class SensorNearestPlayers extends Sensor { ++ ++ public SensorNearestPlayers() {} ++ ++ @Override ++ public Set> a() { ++ return ImmutableSet.of(MemoryModuleType.NEAREST_PLAYERS, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER); ++ } ++ ++ @Override ++ protected void a(WorldServer worldserver, EntityLiving entityliving) { ++ Stream stream = worldserver.getPlayers().stream().filter(IEntitySelector.g).filter((entityplayer) -> { ++ return entityliving.a((Entity) entityplayer, 16.0D); ++ }); ++ ++ entityliving.getClass(); ++ List list = (List) stream.sorted(Comparator.comparingDouble(entityliving::h)).collect(Collectors.toList()); ++ BehaviorController behaviorcontroller = entityliving.getBehaviorController(); ++ ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_PLAYERS, (Object) list); ++ List list1 = (List) list.stream().filter((entityhuman) -> { ++ return a(entityliving, (EntityLiving) entityhuman); ++ }).collect(Collectors.toList()); ++ ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, (Object) (list1.isEmpty() ? null : (EntityHuman) list1.get(0))); ++ Optional optional = list1.stream().filter(IEntitySelector.f).findFirst(); ++ ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, optional); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorVillagerBabies.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorVillagerBabies.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e5e246143f014d78dac765c97a5d9e3ac91a7793 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorVillagerBabies.java +@@ -0,0 +1,38 @@ ++package net.minecraft.world.entity.ai.sensing; ++ ++import com.google.common.collect.ImmutableSet; ++import com.google.common.collect.Lists; ++import java.util.List; ++import java.util.Set; ++import java.util.stream.Collectors; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++ ++public class SensorVillagerBabies extends Sensor { ++ ++ public SensorVillagerBabies() {} ++ ++ @Override ++ public Set> a() { ++ return ImmutableSet.of(MemoryModuleType.VISIBLE_VILLAGER_BABIES); ++ } ++ ++ @Override ++ protected void a(WorldServer worldserver, EntityLiving entityliving) { ++ entityliving.getBehaviorController().setMemory(MemoryModuleType.VISIBLE_VILLAGER_BABIES, (Object) this.a(entityliving)); ++ } ++ ++ private List a(EntityLiving entityliving) { ++ return (List) this.c(entityliving).stream().filter(this::b).collect(Collectors.toList()); ++ } ++ ++ private boolean b(EntityLiving entityliving) { ++ return entityliving.getEntityType() == EntityTypes.VILLAGER && entityliving.isBaby(); ++ } ++ ++ private List c(EntityLiving entityliving) { ++ return (List) entityliving.getBehaviorController().getMemory(MemoryModuleType.VISIBLE_MOBS).orElse(Lists.newArrayList()); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/PathfinderTargetCondition.java b/src/main/java/net/minecraft/world/entity/ai/targeting/PathfinderTargetCondition.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0de32bcf24a94efe5af922b877d4cdc3578e0cbd +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/targeting/PathfinderTargetCondition.java +@@ -0,0 +1,101 @@ ++package net.minecraft.world.entity.ai.targeting; ++ ++import java.util.function.Predicate; ++import javax.annotation.Nullable; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.EntityLiving; ++ ++public class PathfinderTargetCondition { ++ ++ public static final PathfinderTargetCondition a = new PathfinderTargetCondition(); ++ private double b = -1.0D; ++ private boolean c; ++ private boolean d; ++ private boolean e; ++ private boolean f; ++ private boolean g = true; ++ private Predicate h; ++ ++ public PathfinderTargetCondition() {} ++ ++ public PathfinderTargetCondition a(double d0) { ++ this.b = d0; ++ return this; ++ } ++ ++ public PathfinderTargetCondition a() { ++ this.c = true; ++ return this; ++ } ++ ++ public PathfinderTargetCondition b() { ++ this.d = true; ++ return this; ++ } ++ ++ public PathfinderTargetCondition c() { ++ this.e = true; ++ return this; ++ } ++ ++ public PathfinderTargetCondition d() { ++ this.f = true; ++ return this; ++ } ++ ++ public PathfinderTargetCondition e() { ++ this.g = false; ++ return this; ++ } ++ ++ public PathfinderTargetCondition a(@Nullable Predicate predicate) { ++ this.h = predicate; ++ return this; ++ } ++ ++ public boolean a(@Nullable EntityLiving entityliving, EntityLiving entityliving1) { ++ if (entityliving == entityliving1) { ++ return false; ++ } else if (entityliving1.isSpectator()) { ++ return false; ++ } else if (!entityliving1.isAlive()) { ++ return false; ++ } else if (!this.c && entityliving1.isInvulnerable()) { ++ return false; ++ } else if (this.h != null && !this.h.test(entityliving1)) { ++ return false; ++ } else { ++ if (entityliving != null) { ++ if (!this.f) { ++ if (!entityliving.c(entityliving1)) { ++ return false; ++ } ++ ++ if (!entityliving.a(entityliving1.getEntityType())) { ++ return false; ++ } ++ } ++ ++ if (!this.d && entityliving.r(entityliving1)) { ++ return false; ++ } ++ ++ if (this.b > 0.0D) { ++ double d0 = this.g ? entityliving1.A(entityliving) : 1.0D; ++ double d1 = Math.max(this.b * d0, 2.0D); ++ double d2 = entityliving.h(entityliving1.locX(), entityliving1.locY(), entityliving1.locZ()); ++ ++ if (d2 > d1 * d1) { ++ return false; ++ } ++ } ++ ++ if (!this.e && entityliving instanceof EntityInsentient && !((EntityInsentient) entityliving).getEntitySenses().a(entityliving1)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/util/RandomPositionGenerator.java b/src/main/java/net/minecraft/world/entity/ai/util/RandomPositionGenerator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..129ea3857969ddb99e15ae817ee3eec67b4c3ccf +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/util/RandomPositionGenerator.java +@@ -0,0 +1,212 @@ ++package net.minecraft.world.entity.ai.util; ++ ++import java.util.Random; ++import java.util.function.Predicate; ++import java.util.function.ToDoubleFunction; ++import javax.annotation.Nullable; ++import net.minecraft.core.BaseBlockPosition; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.IPosition; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsFluid; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.EntityCreature; ++import net.minecraft.world.entity.ai.navigation.NavigationAbstract; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.pathfinder.PathType; ++import net.minecraft.world.level.pathfinder.PathfinderNormal; ++import net.minecraft.world.phys.Vec3D; ++ ++public class RandomPositionGenerator { ++ ++ @Nullable ++ public static Vec3D a(EntityCreature entitycreature, int i, int j) { ++ return a(entitycreature, i, j, 0, (Vec3D) null, true, 1.5707963705062866D, entitycreature::f, false, 0, 0, true); ++ } ++ ++ @Nullable ++ public static Vec3D a(EntityCreature entitycreature, int i, int j, int k, @Nullable Vec3D vec3d, double d0) { ++ return a(entitycreature, i, j, k, vec3d, true, d0, entitycreature::f, true, 0, 0, false); ++ } ++ ++ @Nullable ++ public static Vec3D b(EntityCreature entitycreature, int i, int j) { ++ entitycreature.getClass(); ++ return a(entitycreature, i, j, entitycreature::f); ++ } ++ ++ @Nullable ++ public static Vec3D a(EntityCreature entitycreature, int i, int j, ToDoubleFunction todoublefunction) { ++ return a(entitycreature, i, j, 0, (Vec3D) null, false, 0.0D, todoublefunction, true, 0, 0, true); ++ } ++ ++ @Nullable ++ public static Vec3D a(EntityCreature entitycreature, int i, int j, Vec3D vec3d, float f, int k, int l) { ++ return a(entitycreature, i, j, 0, vec3d, false, (double) f, entitycreature::f, true, k, l, true); ++ } ++ ++ @Nullable ++ public static Vec3D a(EntityCreature entitycreature, int i, int j, Vec3D vec3d) { ++ Vec3D vec3d1 = vec3d.a(entitycreature.locX(), entitycreature.locY(), entitycreature.locZ()); ++ ++ return a(entitycreature, i, j, 0, vec3d1, false, 1.5707963705062866D, entitycreature::f, true, 0, 0, true); ++ } ++ ++ @Nullable ++ public static Vec3D b(EntityCreature entitycreature, int i, int j, Vec3D vec3d) { ++ Vec3D vec3d1 = vec3d.a(entitycreature.locX(), entitycreature.locY(), entitycreature.locZ()); ++ ++ return a(entitycreature, i, j, 0, vec3d1, true, 1.5707963705062866D, entitycreature::f, false, 0, 0, true); ++ } ++ ++ @Nullable ++ public static Vec3D a(EntityCreature entitycreature, int i, int j, Vec3D vec3d, double d0) { ++ Vec3D vec3d1 = vec3d.a(entitycreature.locX(), entitycreature.locY(), entitycreature.locZ()); ++ ++ return a(entitycreature, i, j, 0, vec3d1, true, d0, entitycreature::f, false, 0, 0, true); ++ } ++ ++ @Nullable ++ public static Vec3D b(EntityCreature entitycreature, int i, int j, int k, Vec3D vec3d, double d0) { ++ Vec3D vec3d1 = vec3d.a(entitycreature.locX(), entitycreature.locY(), entitycreature.locZ()); ++ ++ return a(entitycreature, i, j, k, vec3d1, false, d0, entitycreature::f, true, 0, 0, false); ++ } ++ ++ @Nullable ++ public static Vec3D c(EntityCreature entitycreature, int i, int j, Vec3D vec3d) { ++ Vec3D vec3d1 = entitycreature.getPositionVector().d(vec3d); ++ ++ return a(entitycreature, i, j, 0, vec3d1, true, 1.5707963705062866D, entitycreature::f, false, 0, 0, true); ++ } ++ ++ @Nullable ++ public static Vec3D d(EntityCreature entitycreature, int i, int j, Vec3D vec3d) { ++ Vec3D vec3d1 = entitycreature.getPositionVector().d(vec3d); ++ ++ return a(entitycreature, i, j, 0, vec3d1, false, 1.5707963705062866D, entitycreature::f, true, 0, 0, true); ++ } ++ ++ @Nullable ++ private static Vec3D a(EntityCreature entitycreature, int i, int j, int k, @Nullable Vec3D vec3d, boolean flag, double d0, ToDoubleFunction todoublefunction, boolean flag1, int l, int i1, boolean flag2) { ++ NavigationAbstract navigationabstract = entitycreature.getNavigation(); ++ Random random = entitycreature.getRandom(); ++ boolean flag3; ++ ++ if (entitycreature.ez()) { ++ flag3 = entitycreature.ew().a((IPosition) entitycreature.getPositionVector(), (double) (entitycreature.ex() + (float) i) + 1.0D); ++ } else { ++ flag3 = false; ++ } ++ ++ boolean flag4 = false; ++ double d1 = Double.NEGATIVE_INFINITY; ++ BlockPosition blockposition = entitycreature.getChunkCoordinates(); ++ ++ for (int j1 = 0; j1 < 10; ++j1) { ++ BlockPosition blockposition1 = a(random, i, j, k, vec3d, d0); ++ ++ if (blockposition1 != null) { ++ int k1 = blockposition1.getX(); ++ int l1 = blockposition1.getY(); ++ int i2 = blockposition1.getZ(); ++ BlockPosition blockposition2; ++ ++ if (entitycreature.ez() && i > 1) { ++ blockposition2 = entitycreature.ew(); ++ if (entitycreature.locX() > (double) blockposition2.getX()) { ++ k1 -= random.nextInt(i / 2); ++ } else { ++ k1 += random.nextInt(i / 2); ++ } ++ ++ if (entitycreature.locZ() > (double) blockposition2.getZ()) { ++ i2 -= random.nextInt(i / 2); ++ } else { ++ i2 += random.nextInt(i / 2); ++ } ++ } ++ ++ blockposition2 = new BlockPosition((double) k1 + entitycreature.locX(), (double) l1 + entitycreature.locY(), (double) i2 + entitycreature.locZ()); ++ if (blockposition2.getY() >= 0 && blockposition2.getY() <= entitycreature.world.getBuildHeight() && (!flag3 || entitycreature.a(blockposition2)) && (!flag2 || navigationabstract.a(blockposition2))) { ++ if (flag1) { ++ blockposition2 = a(blockposition2, random.nextInt(l + 1) + i1, entitycreature.world.getBuildHeight(), (blockposition3) -> { ++ return entitycreature.world.getType(blockposition3).getMaterial().isBuildable(); ++ }); ++ } ++ ++ if (flag || !entitycreature.world.getFluid(blockposition2).a((Tag) TagsFluid.WATER)) { ++ PathType pathtype = PathfinderNormal.a((IBlockAccess) entitycreature.world, blockposition2.i()); ++ ++ if (entitycreature.a(pathtype) == 0.0F) { ++ double d2 = todoublefunction.applyAsDouble(blockposition2); ++ ++ if (d2 > d1) { ++ d1 = d2; ++ blockposition = blockposition2; ++ flag4 = true; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ if (flag4) { ++ return Vec3D.c((BaseBlockPosition) blockposition); ++ } else { ++ return null; ++ } ++ } ++ ++ @Nullable ++ private static BlockPosition a(Random random, int i, int j, int k, @Nullable Vec3D vec3d, double d0) { ++ if (vec3d != null && d0 < 3.141592653589793D) { ++ double d1 = MathHelper.d(vec3d.z, vec3d.x) - 1.5707963705062866D; ++ double d2 = d1 + (double) (2.0F * random.nextFloat() - 1.0F) * d0; ++ double d3 = Math.sqrt(random.nextDouble()) * (double) MathHelper.a * (double) i; ++ double d4 = -d3 * Math.sin(d2); ++ double d5 = d3 * Math.cos(d2); ++ ++ if (Math.abs(d4) <= (double) i && Math.abs(d5) <= (double) i) { ++ int l = random.nextInt(2 * j + 1) - j + k; ++ ++ return new BlockPosition(d4, (double) l, d5); ++ } else { ++ return null; ++ } ++ } else { ++ int i1 = random.nextInt(2 * i + 1) - i; ++ int j1 = random.nextInt(2 * j + 1) - j + k; ++ int k1 = random.nextInt(2 * i + 1) - i; ++ ++ return new BlockPosition(i1, j1, k1); ++ } ++ } ++ ++ static BlockPosition a(BlockPosition blockposition, int i, int j, Predicate predicate) { ++ if (i < 0) { ++ throw new IllegalArgumentException("aboveSolidAmount was " + i + ", expected >= 0"); ++ } else if (!predicate.test(blockposition)) { ++ return blockposition; ++ } else { ++ BlockPosition blockposition1; ++ ++ for (blockposition1 = blockposition.up(); blockposition1.getY() < j && predicate.test(blockposition1); blockposition1 = blockposition1.up()) { ++ ; ++ } ++ ++ BlockPosition blockposition2; ++ BlockPosition blockposition3; ++ ++ for (blockposition3 = blockposition1; blockposition3.getY() < j && blockposition3.getY() - blockposition1.getY() < i; blockposition3 = blockposition2) { ++ blockposition2 = blockposition3.up(); ++ if (predicate.test(blockposition2)) { ++ break; ++ } ++ } ++ ++ return blockposition3; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fe5dcce3873ca2724ac9d416d6f5d3c65d0fdafe +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java +@@ -0,0 +1,274 @@ ++package net.minecraft.world.entity.ai.village.poi; ++ ++import com.mojang.datafixers.DataFixer; ++import com.mojang.datafixers.util.Pair; ++import it.unimi.dsi.fastutil.longs.Long2ByteMap; ++import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongOpenHashSet; ++import it.unimi.dsi.fastutil.longs.LongSet; ++import java.io.File; ++import java.util.Collections; ++import java.util.Comparator; ++import java.util.List; ++import java.util.Optional; ++import java.util.Random; ++import java.util.Set; ++import java.util.function.BiConsumer; ++import java.util.function.BooleanSupplier; ++import java.util.function.Predicate; ++import java.util.stream.Collectors; ++import java.util.stream.IntStream; ++import java.util.stream.Stream; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.server.level.LightEngineGraphSection; ++import net.minecraft.util.datafix.DataFixTypes; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.IWorldReader; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.ChunkSection; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.storage.RegionFileSection; ++ ++public class VillagePlace extends RegionFileSection { ++ ++ private final VillagePlace.a a = new VillagePlace.a(); ++ private final LongSet b = new LongOpenHashSet(); ++ ++ public VillagePlace(File file, DataFixer datafixer, boolean flag) { ++ super(file, VillagePlaceSection::a, VillagePlaceSection::new, datafixer, DataFixTypes.POI_CHUNK, flag); ++ } ++ ++ public void a(BlockPosition blockposition, VillagePlaceType villageplacetype) { ++ ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).a(blockposition, villageplacetype); ++ } ++ ++ public void a(BlockPosition blockposition) { ++ ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).a(blockposition); ++ } ++ ++ public long a(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { ++ return this.c(predicate, blockposition, i, villageplace_occupancy).count(); ++ } ++ ++ public boolean a(VillagePlaceType villageplacetype, BlockPosition blockposition) { ++ Optional optional = ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).d(blockposition); ++ ++ return optional.isPresent() && ((VillagePlaceType) optional.get()).equals(villageplacetype); ++ } ++ ++ public Stream b(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { ++ int j = Math.floorDiv(i, 16) + 1; ++ ++ return ChunkCoordIntPair.a(new ChunkCoordIntPair(blockposition), j).flatMap((chunkcoordintpair) -> { ++ return this.a(predicate, chunkcoordintpair, villageplace_occupancy); ++ }).filter((villageplacerecord) -> { ++ BlockPosition blockposition1 = villageplacerecord.f(); ++ ++ return Math.abs(blockposition1.getX() - blockposition.getX()) <= i && Math.abs(blockposition1.getZ() - blockposition.getZ()) <= i; ++ }); ++ } ++ ++ public Stream c(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { ++ int j = i * i; ++ ++ return this.b(predicate, blockposition, i, villageplace_occupancy).filter((villageplacerecord) -> { ++ return villageplacerecord.f().j(blockposition) <= (double) j; ++ }); ++ } ++ ++ public Stream a(Predicate predicate, ChunkCoordIntPair chunkcoordintpair, VillagePlace.Occupancy villageplace_occupancy) { ++ return IntStream.range(0, 16).boxed().map((integer) -> { ++ return this.d(SectionPosition.a(chunkcoordintpair, integer).s()); ++ }).filter(Optional::isPresent).flatMap((optional) -> { ++ return ((VillagePlaceSection) optional.get()).a(predicate, villageplace_occupancy); ++ }); ++ } ++ ++ public Stream a(Predicate predicate, Predicate predicate1, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { ++ return this.c(predicate, blockposition, i, villageplace_occupancy).map(VillagePlaceRecord::f).filter(predicate1); ++ } ++ ++ public Stream b(Predicate predicate, Predicate predicate1, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { ++ return this.a(predicate, predicate1, blockposition, i, villageplace_occupancy).sorted(Comparator.comparingDouble((blockposition1) -> { ++ return blockposition1.j(blockposition); ++ })); ++ } ++ ++ public Optional c(Predicate predicate, Predicate predicate1, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { ++ return this.a(predicate, predicate1, blockposition, i, villageplace_occupancy).findFirst(); ++ } ++ ++ public Optional d(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { ++ return this.c(predicate, blockposition, i, villageplace_occupancy).map(VillagePlaceRecord::f).min(Comparator.comparingDouble((blockposition1) -> { ++ return blockposition1.j(blockposition); ++ })); ++ } ++ ++ public Optional a(Predicate predicate, Predicate predicate1, BlockPosition blockposition, int i) { ++ return this.c(predicate, blockposition, i, VillagePlace.Occupancy.HAS_SPACE).filter((villageplacerecord) -> { ++ return predicate1.test(villageplacerecord.f()); ++ }).findFirst().map((villageplacerecord) -> { ++ villageplacerecord.b(); ++ return villageplacerecord.f(); ++ }); ++ } ++ ++ public Optional a(Predicate predicate, Predicate predicate1, VillagePlace.Occupancy villageplace_occupancy, BlockPosition blockposition, int i, Random random) { ++ List list = (List) this.c(predicate, blockposition, i, villageplace_occupancy).collect(Collectors.toList()); ++ ++ Collections.shuffle(list, random); ++ return list.stream().filter((villageplacerecord) -> { ++ return predicate1.test(villageplacerecord.f()); ++ }).findFirst().map(VillagePlaceRecord::f); ++ } ++ ++ public boolean b(BlockPosition blockposition) { ++ return ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).c(blockposition); ++ } ++ ++ public boolean a(BlockPosition blockposition, Predicate predicate) { ++ return (Boolean) this.d(SectionPosition.a(blockposition).s()).map((villageplacesection) -> { ++ return villageplacesection.a(blockposition, predicate); ++ }).orElse(false); ++ } ++ ++ public Optional c(BlockPosition blockposition) { ++ VillagePlaceSection villageplacesection = (VillagePlaceSection) this.e(SectionPosition.a(blockposition).s()); ++ ++ return villageplacesection.d(blockposition); ++ } ++ ++ public int a(SectionPosition sectionposition) { ++ this.a.a(); ++ return this.a.c(sectionposition.s()); ++ } ++ ++ private boolean f(long i) { ++ Optional optional = this.c(i); ++ ++ return optional == null ? false : (Boolean) optional.map((villageplacesection) -> { ++ return villageplacesection.a(VillagePlaceType.b, VillagePlace.Occupancy.IS_OCCUPIED).count() > 0L; ++ }).orElse(false); ++ } ++ ++ @Override ++ public void a(BooleanSupplier booleansupplier) { ++ super.a(booleansupplier); ++ this.a.a(); ++ } ++ ++ @Override ++ protected void a(long i) { ++ super.a(i); ++ this.a.b(i, this.a.b(i), false); ++ } ++ ++ @Override ++ protected void b(long i) { ++ this.a.b(i, this.a.b(i), false); ++ } ++ ++ public void a(ChunkCoordIntPair chunkcoordintpair, ChunkSection chunksection) { ++ SectionPosition sectionposition = SectionPosition.a(chunkcoordintpair, chunksection.getYPosition() >> 4); ++ ++ SystemUtils.a(this.d(sectionposition.s()), (villageplacesection) -> { ++ villageplacesection.a((biconsumer) -> { ++ if (a(chunksection)) { ++ this.a(chunksection, sectionposition, biconsumer); ++ } ++ ++ }); ++ }, () -> { ++ if (a(chunksection)) { ++ VillagePlaceSection villageplacesection = (VillagePlaceSection) this.e(sectionposition.s()); ++ ++ this.a(chunksection, sectionposition, villageplacesection::a); ++ } ++ ++ }); ++ } ++ ++ private static boolean a(ChunkSection chunksection) { ++ Set set = VillagePlaceType.x; ++ ++ set.getClass(); ++ return chunksection.a(set::contains); ++ } ++ ++ private void a(ChunkSection chunksection, SectionPosition sectionposition, BiConsumer biconsumer) { ++ sectionposition.t().forEach((blockposition) -> { ++ IBlockData iblockdata = chunksection.getType(SectionPosition.b(blockposition.getX()), SectionPosition.b(blockposition.getY()), SectionPosition.b(blockposition.getZ())); ++ ++ VillagePlaceType.b(iblockdata).ifPresent((villageplacetype) -> { ++ biconsumer.accept(blockposition, villageplacetype); ++ }); ++ }); ++ } ++ ++ public void a(IWorldReader iworldreader, BlockPosition blockposition, int i) { ++ SectionPosition.b(new ChunkCoordIntPair(blockposition), Math.floorDiv(i, 16)).map((sectionposition) -> { ++ return Pair.of(sectionposition, this.d(sectionposition.s())); ++ }).filter((pair) -> { ++ return !(Boolean) ((Optional) pair.getSecond()).map(VillagePlaceSection::a).orElse(false); ++ }).map((pair) -> { ++ return ((SectionPosition) pair.getFirst()).r(); ++ }).filter((chunkcoordintpair) -> { ++ return this.b.add(chunkcoordintpair.pair()); ++ }).forEach((chunkcoordintpair) -> { ++ iworldreader.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.EMPTY); ++ }); ++ } ++ ++ final class a extends LightEngineGraphSection { ++ ++ private final Long2ByteMap b = new Long2ByteOpenHashMap(); ++ ++ protected a() { ++ super(7, 16, 256); ++ this.b.defaultReturnValue((byte) 7); ++ } ++ ++ @Override ++ protected int b(long i) { ++ return VillagePlace.this.f(i) ? 0 : 7; ++ } ++ ++ @Override ++ protected int c(long i) { ++ return this.b.get(i); ++ } ++ ++ @Override ++ protected void a(long i, int j) { ++ if (j > 6) { ++ this.b.remove(i); ++ } else { ++ this.b.put(i, (byte) j); ++ } ++ ++ } ++ ++ public void a() { ++ super.b(Integer.MAX_VALUE); ++ } ++ } ++ ++ public static enum Occupancy { ++ ++ HAS_SPACE(VillagePlaceRecord::d), IS_OCCUPIED(VillagePlaceRecord::e), ANY((villageplacerecord) -> { ++ return true; ++ }); ++ ++ private final Predicate d; ++ ++ private Occupancy(Predicate predicate) { ++ this.d = predicate; ++ } ++ ++ public Predicate a() { ++ return this.d; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceRecord.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceRecord.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3fcbf223c4835b92a7da0df9e5443ca1e5217bb4 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceRecord.java +@@ -0,0 +1,82 @@ ++package net.minecraft.world.entity.ai.village.poi; ++ ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.codecs.RecordCodecBuilder; ++import java.util.Objects; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.IRegistry; ++ ++public class VillagePlaceRecord { ++ ++ private final BlockPosition a; ++ private final VillagePlaceType b; ++ private int c; ++ private final Runnable d; ++ ++ public static Codec a(Runnable runnable) { ++ return RecordCodecBuilder.create((instance) -> { ++ return instance.group(BlockPosition.a.fieldOf("pos").forGetter((villageplacerecord) -> { ++ return villageplacerecord.a; ++ }), IRegistry.POINT_OF_INTEREST_TYPE.fieldOf("type").forGetter((villageplacerecord) -> { ++ return villageplacerecord.b; ++ }), Codec.INT.fieldOf("free_tickets").orElse(0).forGetter((villageplacerecord) -> { ++ return villageplacerecord.c; ++ }), RecordCodecBuilder.point(runnable)).apply(instance, VillagePlaceRecord::new); ++ }); ++ } ++ ++ private VillagePlaceRecord(BlockPosition blockposition, VillagePlaceType villageplacetype, int i, Runnable runnable) { ++ this.a = blockposition.immutableCopy(); ++ this.b = villageplacetype; ++ this.c = i; ++ this.d = runnable; ++ } ++ ++ public VillagePlaceRecord(BlockPosition blockposition, VillagePlaceType villageplacetype, Runnable runnable) { ++ this(blockposition, villageplacetype, villageplacetype.b(), runnable); ++ } ++ ++ protected boolean b() { ++ if (this.c <= 0) { ++ return false; ++ } else { ++ --this.c; ++ this.d.run(); ++ return true; ++ } ++ } ++ ++ protected boolean c() { ++ if (this.c >= this.b.b()) { ++ return false; ++ } else { ++ ++this.c; ++ this.d.run(); ++ return true; ++ } ++ } ++ ++ public boolean d() { ++ return this.c > 0; ++ } ++ ++ public boolean e() { ++ return this.c != this.b.b(); ++ } ++ ++ public BlockPosition f() { ++ return this.a; ++ } ++ ++ public VillagePlaceType g() { ++ return this.b; ++ } ++ ++ public boolean equals(Object object) { ++ return this == object ? true : (object != null && this.getClass() == object.getClass() ? Objects.equals(this.a, ((VillagePlaceRecord) object).a) : false); ++ } ++ ++ public int hashCode() { ++ return this.a.hashCode(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceSection.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a72bd9ce8f683ae3f4d9c08c973292e3433e8f16 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceSection.java +@@ -0,0 +1,175 @@ ++package net.minecraft.world.entity.ai.village.poi; ++ ++import com.google.common.collect.ImmutableList; ++import com.google.common.collect.Maps; ++import com.google.common.collect.Sets; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.codecs.RecordCodecBuilder; ++import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; ++import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.Optional; ++import java.util.Set; ++import java.util.function.BiConsumer; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++import java.util.stream.Stream; ++import net.minecraft.SharedConstants; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.SectionPosition; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++import org.apache.logging.log4j.util.Supplier; ++ ++public class VillagePlaceSection { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final Short2ObjectMap b; ++ private final Map> c; ++ private final Runnable d; ++ private boolean e; ++ ++ public static Codec a(Runnable runnable) { ++ Codec codec = RecordCodecBuilder.create((instance) -> { ++ return instance.group(RecordCodecBuilder.point(runnable), Codec.BOOL.optionalFieldOf("Valid", false).forGetter((villageplacesection) -> { ++ return villageplacesection.e; ++ }), VillagePlaceRecord.a(runnable).listOf().fieldOf("Records").forGetter((villageplacesection) -> { ++ return ImmutableList.copyOf(villageplacesection.b.values()); ++ })).apply(instance, VillagePlaceSection::new); ++ }); ++ Logger logger = VillagePlaceSection.LOGGER; ++ ++ logger.getClass(); ++ return codec.orElseGet(SystemUtils.a("Failed to read POI section: ", logger::error), () -> { ++ return new VillagePlaceSection(runnable, false, ImmutableList.of()); ++ }); ++ } ++ ++ public VillagePlaceSection(Runnable runnable) { ++ this(runnable, true, ImmutableList.of()); ++ } ++ ++ private VillagePlaceSection(Runnable runnable, boolean flag, List list) { ++ this.b = new Short2ObjectOpenHashMap(); ++ this.c = Maps.newHashMap(); ++ this.d = runnable; ++ this.e = flag; ++ list.forEach(this::a); ++ } ++ ++ public Stream a(Predicate predicate, VillagePlace.Occupancy villageplace_occupancy) { ++ return this.c.entrySet().stream().filter((entry) -> { ++ return predicate.test(entry.getKey()); ++ }).flatMap((entry) -> { ++ return ((Set) entry.getValue()).stream(); ++ }).filter(villageplace_occupancy.a()); ++ } ++ ++ public void a(BlockPosition blockposition, VillagePlaceType villageplacetype) { ++ if (this.a(new VillagePlaceRecord(blockposition, villageplacetype, this.d))) { ++ VillagePlaceSection.LOGGER.debug("Added POI of type {} @ {}", new Supplier[]{() -> { ++ return villageplacetype; ++ }, () -> { ++ return blockposition; ++ }}); ++ this.d.run(); ++ } ++ ++ } ++ ++ private boolean a(VillagePlaceRecord villageplacerecord) { ++ BlockPosition blockposition = villageplacerecord.f(); ++ VillagePlaceType villageplacetype = villageplacerecord.g(); ++ short short0 = SectionPosition.b(blockposition); ++ VillagePlaceRecord villageplacerecord1 = (VillagePlaceRecord) this.b.get(short0); ++ ++ if (villageplacerecord1 != null) { ++ if (villageplacetype.equals(villageplacerecord1.g())) { ++ return false; ++ } ++ ++ String s = "POI data mismatch: already registered at " + blockposition; ++ ++ if (SharedConstants.d) { ++ throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException(s))); ++ } ++ ++ VillagePlaceSection.LOGGER.error(s); ++ } ++ ++ this.b.put(short0, villageplacerecord); ++ ((Set) this.c.computeIfAbsent(villageplacetype, (villageplacetype1) -> { ++ return Sets.newHashSet(); ++ })).add(villageplacerecord); ++ return true; ++ } ++ ++ public void a(BlockPosition blockposition) { ++ VillagePlaceRecord villageplacerecord = (VillagePlaceRecord) this.b.remove(SectionPosition.b(blockposition)); ++ ++ if (villageplacerecord == null) { ++ VillagePlaceSection.LOGGER.error("POI data mismatch: never registered at " + blockposition); ++ } else { ++ ((Set) this.c.get(villageplacerecord.g())).remove(villageplacerecord); ++ VillagePlaceSection.LOGGER.debug("Removed POI of type {} @ {}", new Supplier[]{villageplacerecord::g, villageplacerecord::f}); ++ this.d.run(); ++ } ++ } ++ ++ public boolean c(BlockPosition blockposition) { ++ VillagePlaceRecord villageplacerecord = (VillagePlaceRecord) this.b.get(SectionPosition.b(blockposition)); ++ ++ if (villageplacerecord == null) { ++ throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException("POI never registered at " + blockposition))); ++ } else { ++ boolean flag = villageplacerecord.c(); ++ ++ this.d.run(); ++ return flag; ++ } ++ } ++ ++ public boolean a(BlockPosition blockposition, Predicate predicate) { ++ short short0 = SectionPosition.b(blockposition); ++ VillagePlaceRecord villageplacerecord = (VillagePlaceRecord) this.b.get(short0); ++ ++ return villageplacerecord != null && predicate.test(villageplacerecord.g()); ++ } ++ ++ public Optional d(BlockPosition blockposition) { ++ short short0 = SectionPosition.b(blockposition); ++ VillagePlaceRecord villageplacerecord = (VillagePlaceRecord) this.b.get(short0); ++ ++ return villageplacerecord != null ? Optional.of(villageplacerecord.g()) : Optional.empty(); ++ } ++ ++ public void a(Consumer> consumer) { ++ if (!this.e) { ++ Short2ObjectMap short2objectmap = new Short2ObjectOpenHashMap(this.b); ++ ++ this.b(); ++ consumer.accept((blockposition, villageplacetype) -> { ++ short short0 = SectionPosition.b(blockposition); ++ VillagePlaceRecord villageplacerecord = (VillagePlaceRecord) short2objectmap.computeIfAbsent(short0, (i) -> { ++ return new VillagePlaceRecord(blockposition, villageplacetype, this.d); ++ }); ++ ++ this.a(villageplacerecord); ++ }); ++ this.e = true; ++ this.d.run(); ++ } ++ ++ } ++ ++ private void b() { ++ this.b.clear(); ++ this.c.clear(); ++ } ++ ++ boolean a() { ++ return this.e; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseChestedAbstract.java b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseChestedAbstract.java +new file mode 100644 +index 0000000000000000000000000000000000000000..aa12a0c9f30cd2b8a6de75ff9822843da808ae64 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseChestedAbstract.java +@@ -0,0 +1,198 @@ ++package net.minecraft.world.entity.animal.horse; ++ ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.network.syncher.DataWatcher; ++import net.minecraft.network.syncher.DataWatcherObject; ++import net.minecraft.network.syncher.DataWatcherRegistry; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.ai.attributes.AttributeProvider; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.level.IMaterial; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Blocks; ++ ++public abstract class EntityHorseChestedAbstract extends EntityHorseAbstract { ++ ++ private static final DataWatcherObject bw = DataWatcher.a(EntityHorseChestedAbstract.class, DataWatcherRegistry.i); ++ ++ protected EntityHorseChestedAbstract(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ this.bu = false; ++ } ++ ++ @Override ++ protected void eK() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue((double) this.fp()); ++ } ++ ++ @Override ++ protected void initDatawatcher() { ++ super.initDatawatcher(); ++ this.datawatcher.register(EntityHorseChestedAbstract.bw, false); ++ } ++ ++ public static AttributeProvider.Builder eL() { ++ return fi().a(GenericAttributes.MOVEMENT_SPEED, 0.17499999701976776D).a(GenericAttributes.JUMP_STRENGTH, 0.5D); ++ } ++ ++ public boolean isCarryingChest() { ++ return (Boolean) this.datawatcher.get(EntityHorseChestedAbstract.bw); ++ } ++ ++ public void setCarryingChest(boolean flag) { ++ this.datawatcher.set(EntityHorseChestedAbstract.bw, flag); ++ } ++ ++ @Override ++ protected int getChestSlots() { ++ return this.isCarryingChest() ? 17 : super.getChestSlots(); ++ } ++ ++ @Override ++ public double bc() { ++ return super.bc() - 0.25D; ++ } ++ ++ @Override ++ protected void dropInventory() { ++ super.dropInventory(); ++ if (this.isCarryingChest()) { ++ if (!this.world.isClientSide) { ++ this.a((IMaterial) Blocks.CHEST); ++ } ++ ++ this.setCarryingChest(false); ++ } ++ ++ } ++ ++ @Override ++ public void saveData(NBTTagCompound nbttagcompound) { ++ super.saveData(nbttagcompound); ++ nbttagcompound.setBoolean("ChestedHorse", this.isCarryingChest()); ++ if (this.isCarryingChest()) { ++ NBTTagList nbttaglist = new NBTTagList(); ++ ++ for (int i = 2; i < this.inventoryChest.getSize(); ++i) { ++ ItemStack itemstack = this.inventoryChest.getItem(i); ++ ++ if (!itemstack.isEmpty()) { ++ NBTTagCompound nbttagcompound1 = new NBTTagCompound(); ++ ++ nbttagcompound1.setByte("Slot", (byte) i); ++ itemstack.save(nbttagcompound1); ++ nbttaglist.add(nbttagcompound1); ++ } ++ } ++ ++ nbttagcompound.set("Items", nbttaglist); ++ } ++ ++ } ++ ++ @Override ++ public void loadData(NBTTagCompound nbttagcompound) { ++ super.loadData(nbttagcompound); ++ this.setCarryingChest(nbttagcompound.getBoolean("ChestedHorse")); ++ if (this.isCarryingChest()) { ++ NBTTagList nbttaglist = nbttagcompound.getList("Items", 10); ++ ++ this.loadChest(); ++ ++ for (int i = 0; i < nbttaglist.size(); ++i) { ++ NBTTagCompound nbttagcompound1 = nbttaglist.getCompound(i); ++ int j = nbttagcompound1.getByte("Slot") & 255; ++ ++ if (j >= 2 && j < this.inventoryChest.getSize()) { ++ this.inventoryChest.setItem(j, ItemStack.a(nbttagcompound1)); ++ } ++ } ++ } ++ ++ this.fe(); ++ } ++ ++ @Override ++ public boolean a_(int i, ItemStack itemstack) { ++ if (i == 499) { ++ if (this.isCarryingChest() && itemstack.isEmpty()) { ++ this.setCarryingChest(false); ++ this.loadChest(); ++ return true; ++ } ++ ++ if (!this.isCarryingChest() && itemstack.getItem() == Blocks.CHEST.getItem()) { ++ this.setCarryingChest(true); ++ this.loadChest(); ++ return true; ++ } ++ } ++ ++ return super.a_(i, itemstack); ++ } ++ ++ @Override ++ public EnumInteractionResult b(EntityHuman entityhuman, EnumHand enumhand) { ++ ItemStack itemstack = entityhuman.b(enumhand); ++ ++ if (!this.isBaby()) { ++ if (this.isTamed() && entityhuman.eq()) { ++ this.f(entityhuman); ++ return EnumInteractionResult.a(this.world.isClientSide); ++ } ++ ++ if (this.isVehicle()) { ++ return super.b(entityhuman, enumhand); ++ } ++ } ++ ++ if (!itemstack.isEmpty()) { ++ if (this.k(itemstack)) { ++ return this.b(entityhuman, itemstack); ++ } ++ ++ if (!this.isTamed()) { ++ this.fm(); ++ return EnumInteractionResult.a(this.world.isClientSide); ++ } ++ ++ if (!this.isCarryingChest() && itemstack.getItem() == Blocks.CHEST.getItem()) { ++ this.setCarryingChest(true); ++ this.eO(); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } ++ ++ this.loadChest(); ++ return EnumInteractionResult.a(this.world.isClientSide); ++ } ++ ++ if (!this.isBaby() && !this.hasSaddle() && itemstack.getItem() == Items.SADDLE) { ++ this.f(entityhuman); ++ return EnumInteractionResult.a(this.world.isClientSide); ++ } ++ } ++ ++ if (this.isBaby()) { ++ return super.b(entityhuman, enumhand); ++ } else { ++ this.h(entityhuman); ++ return EnumInteractionResult.a(this.world.isClientSide); ++ } ++ } ++ ++ protected void eO() { ++ this.playSound(SoundEffects.ENTITY_DONKEY_CHEST, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); ++ } ++ ++ public int eU() { ++ return 5; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseSkeleton.java b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseSkeleton.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0ddb676a2d1c38c97cd7750c1594fd6468156d0a +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseSkeleton.java +@@ -0,0 +1,201 @@ ++package net.minecraft.world.entity.animal.horse; ++ ++import javax.annotation.Nullable; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.sounds.SoundEffect; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsFluid; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.EntityAgeable; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.EnumMonsterType; ++import net.minecraft.world.entity.ai.attributes.AttributeProvider; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; ++import net.minecraft.world.entity.ai.goal.PathfinderGoal; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.level.World; ++ ++public class EntityHorseSkeleton extends EntityHorseAbstract { ++ ++ private final PathfinderGoalHorseTrap bw = new PathfinderGoalHorseTrap(this); ++ private boolean bx; ++ private int by; ++ ++ public EntityHorseSkeleton(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ } ++ ++ public static AttributeProvider.Builder eL() { ++ return fi().a(GenericAttributes.MAX_HEALTH, 15.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.20000000298023224D); ++ } ++ ++ @Override ++ protected void eK() { ++ this.getAttributeInstance(GenericAttributes.JUMP_STRENGTH).setValue(this.fq()); ++ } ++ ++ @Override ++ protected void eV() {} ++ ++ @Override ++ protected SoundEffect getSoundAmbient() { ++ super.getSoundAmbient(); ++ return this.a((Tag) TagsFluid.WATER) ? SoundEffects.ENTITY_SKELETON_HORSE_AMBIENT_WATER : SoundEffects.ENTITY_SKELETON_HORSE_AMBIENT; ++ } ++ ++ @Override ++ protected SoundEffect getSoundDeath() { ++ super.getSoundDeath(); ++ return SoundEffects.ENTITY_SKELETON_HORSE_DEATH; ++ } ++ ++ @Override ++ protected SoundEffect getSoundHurt(DamageSource damagesource) { ++ super.getSoundHurt(damagesource); ++ return SoundEffects.ENTITY_SKELETON_HORSE_HURT; ++ } ++ ++ @Override ++ protected SoundEffect getSoundSwim() { ++ if (this.onGround) { ++ if (!this.isVehicle()) { ++ return SoundEffects.ENTITY_SKELETON_HORSE_STEP_WATER; ++ } ++ ++ ++this.bv; ++ if (this.bv > 5 && this.bv % 3 == 0) { ++ return SoundEffects.ENTITY_SKELETON_HORSE_GALLOP_WATER; ++ } ++ ++ if (this.bv <= 5) { ++ return SoundEffects.ENTITY_SKELETON_HORSE_STEP_WATER; ++ } ++ } ++ ++ return SoundEffects.ENTITY_SKELETON_HORSE_SWIM; ++ } ++ ++ @Override ++ protected void d(float f) { ++ if (this.onGround) { ++ super.d(0.3F); ++ } else { ++ super.d(Math.min(0.1F, f * 25.0F)); ++ } ++ ++ } ++ ++ @Override ++ protected void fn() { ++ if (this.isInWater()) { ++ this.playSound(SoundEffects.ENTITY_SKELETON_HORSE_JUMP_WATER, 0.4F, 1.0F); ++ } else { ++ super.fn(); ++ } ++ ++ } ++ ++ @Override ++ public EnumMonsterType getMonsterType() { ++ return EnumMonsterType.UNDEAD; ++ } ++ ++ @Override ++ public double bc() { ++ return super.bc() - 0.1875D; ++ } ++ ++ @Override ++ public void movementTick() { ++ super.movementTick(); ++ if (this.eM() && this.by++ >= 18000) { ++ this.die(); ++ } ++ ++ } ++ ++ @Override ++ public void saveData(NBTTagCompound nbttagcompound) { ++ super.saveData(nbttagcompound); ++ nbttagcompound.setBoolean("SkeletonTrap", this.eM()); ++ nbttagcompound.setInt("SkeletonTrapTime", this.by); ++ } ++ ++ @Override ++ public void loadData(NBTTagCompound nbttagcompound) { ++ super.loadData(nbttagcompound); ++ this.t(nbttagcompound.getBoolean("SkeletonTrap")); ++ this.by = nbttagcompound.getInt("SkeletonTrapTime"); ++ } ++ ++ @Override ++ public boolean bt() { ++ return true; ++ } ++ ++ @Override ++ protected float dM() { ++ return 0.96F; ++ } ++ ++ public boolean eM() { ++ return this.bx; ++ } ++ ++ public void t(boolean flag) { ++ if (flag != this.bx) { ++ this.bx = flag; ++ if (flag) { ++ this.goalSelector.a(1, this.bw); ++ } else { ++ this.goalSelector.a((PathfinderGoal) this.bw); ++ } ++ ++ } ++ } ++ ++ @Nullable ++ @Override ++ public EntityAgeable createChild(WorldServer worldserver, EntityAgeable entityageable) { ++ return (EntityAgeable) EntityTypes.SKELETON_HORSE.a((World) worldserver); ++ } ++ ++ @Override ++ public EnumInteractionResult b(EntityHuman entityhuman, EnumHand enumhand) { ++ ItemStack itemstack = entityhuman.b(enumhand); ++ ++ if (!this.isTamed()) { ++ return EnumInteractionResult.PASS; ++ } else if (this.isBaby()) { ++ return super.b(entityhuman, enumhand); ++ } else if (entityhuman.eq()) { ++ this.f(entityhuman); ++ return EnumInteractionResult.a(this.world.isClientSide); ++ } else if (this.isVehicle()) { ++ return super.b(entityhuman, enumhand); ++ } else { ++ if (!itemstack.isEmpty()) { ++ if (itemstack.getItem() == Items.SADDLE && !this.hasSaddle()) { ++ this.f(entityhuman); ++ return EnumInteractionResult.a(this.world.isClientSide); ++ } ++ ++ EnumInteractionResult enuminteractionresult = itemstack.a(entityhuman, (EntityLiving) this, enumhand); ++ ++ if (enuminteractionresult.a()) { ++ return enuminteractionresult; ++ } ++ } ++ ++ this.h(entityhuman); ++ return EnumInteractionResult.a(this.world.isClientSide); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/EntityLlama.java b/src/main/java/net/minecraft/world/entity/animal/horse/EntityLlama.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8b09aaa30dd753fd34bea155890bdd9e5cb180f5 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/EntityLlama.java +@@ -0,0 +1,533 @@ ++package net.minecraft.world.entity.animal.horse; ++ ++import java.util.Iterator; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.particles.Particles; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.network.syncher.DataWatcher; ++import net.minecraft.network.syncher.DataWatcherObject; ++import net.minecraft.network.syncher.DataWatcherRegistry; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.sounds.SoundEffect; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.tags.TagsItem; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.DifficultyDamageScaler; ++import net.minecraft.world.IInventory; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityAgeable; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.EnumMobSpawn; ++import net.minecraft.world.entity.GroupDataEntity; ++import net.minecraft.world.entity.ai.attributes.AttributeProvider; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalArrowAttack; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalBreed; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalFloat; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalFollowParent; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalLlamaFollow; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalLookAtPlayer; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalPanic; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalRandomLookaround; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalRandomStrollLand; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalTame; ++import net.minecraft.world.entity.ai.goal.target.PathfinderGoalHurtByTarget; ++import net.minecraft.world.entity.ai.goal.target.PathfinderGoalNearestAttackableTarget; ++import net.minecraft.world.entity.animal.EntityAnimal; ++import net.minecraft.world.entity.animal.EntityWolf; ++import net.minecraft.world.entity.monster.IRangedEntity; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.projectile.EntityLlamaSpit; ++import net.minecraft.world.item.EnumColor; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.item.crafting.RecipeItemStack; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.WorldAccess; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.BlockCarpet; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++ ++public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEntity { ++ ++ private static final RecipeItemStack bw = RecipeItemStack.a(Items.WHEAT, Blocks.HAY_BLOCK.getItem()); ++ private static final DataWatcherObject bx = DataWatcher.a(EntityLlama.class, DataWatcherRegistry.b); ++ private static final DataWatcherObject by = DataWatcher.a(EntityLlama.class, DataWatcherRegistry.b); ++ private static final DataWatcherObject bz = DataWatcher.a(EntityLlama.class, DataWatcherRegistry.b); ++ private boolean bA; ++ @Nullable ++ private EntityLlama bB; ++ @Nullable ++ private EntityLlama bC; ++ ++ public EntityLlama(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ } ++ ++ public void setStrength(int i) { ++ this.datawatcher.set(EntityLlama.bx, Math.max(1, Math.min(5, i))); ++ } ++ ++ private void fE() { ++ int i = this.random.nextFloat() < 0.04F ? 5 : 3; ++ ++ this.setStrength(1 + this.random.nextInt(i)); ++ } ++ ++ public int getStrength() { ++ return (Integer) this.datawatcher.get(EntityLlama.bx); ++ } ++ ++ @Override ++ public void saveData(NBTTagCompound nbttagcompound) { ++ super.saveData(nbttagcompound); ++ nbttagcompound.setInt("Variant", this.getVariant()); ++ nbttagcompound.setInt("Strength", this.getStrength()); ++ if (!this.inventoryChest.getItem(1).isEmpty()) { ++ nbttagcompound.set("DecorItem", this.inventoryChest.getItem(1).save(new NBTTagCompound())); ++ } ++ ++ } ++ ++ @Override ++ public void loadData(NBTTagCompound nbttagcompound) { ++ this.setStrength(nbttagcompound.getInt("Strength")); ++ super.loadData(nbttagcompound); ++ this.setVariant(nbttagcompound.getInt("Variant")); ++ if (nbttagcompound.hasKeyOfType("DecorItem", 10)) { ++ this.inventoryChest.setItem(1, ItemStack.a(nbttagcompound.getCompound("DecorItem"))); ++ } ++ ++ this.fe(); ++ } ++ ++ @Override ++ protected void initPathfinder() { ++ this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(1, new PathfinderGoalTame(this, 1.2D)); ++ this.goalSelector.a(2, new PathfinderGoalLlamaFollow(this, 2.0999999046325684D)); ++ this.goalSelector.a(3, new PathfinderGoalArrowAttack(this, 1.25D, 40, 20.0F)); ++ this.goalSelector.a(3, new PathfinderGoalPanic(this, 1.2D)); ++ this.goalSelector.a(4, new PathfinderGoalBreed(this, 1.0D)); ++ this.goalSelector.a(5, new PathfinderGoalFollowParent(this, 1.0D)); ++ this.goalSelector.a(6, new PathfinderGoalRandomStrollLand(this, 0.7D)); ++ this.goalSelector.a(7, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F)); ++ this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(1, new EntityLlama.c(this)); ++ this.targetSelector.a(2, new EntityLlama.a(this)); ++ } ++ ++ public static AttributeProvider.Builder fw() { ++ return eL().a(GenericAttributes.FOLLOW_RANGE, 40.0D); ++ } ++ ++ @Override ++ protected void initDatawatcher() { ++ super.initDatawatcher(); ++ this.datawatcher.register(EntityLlama.bx, 0); ++ this.datawatcher.register(EntityLlama.by, -1); ++ this.datawatcher.register(EntityLlama.bz, 0); ++ } ++ ++ public int getVariant() { ++ return MathHelper.clamp((Integer) this.datawatcher.get(EntityLlama.bz), 0, 3); ++ } ++ ++ public void setVariant(int i) { ++ this.datawatcher.set(EntityLlama.bz, i); ++ } ++ ++ @Override ++ protected int getChestSlots() { ++ return this.isCarryingChest() ? 2 + 3 * this.eU() : super.getChestSlots(); ++ } ++ ++ @Override ++ public void k(Entity entity) { ++ if (this.w(entity)) { ++ float f = MathHelper.cos(this.aA * 0.017453292F); ++ float f1 = MathHelper.sin(this.aA * 0.017453292F); ++ float f2 = 0.3F; ++ ++ entity.setPosition(this.locX() + (double) (0.3F * f1), this.locY() + this.bc() + entity.bb(), this.locZ() - (double) (0.3F * f)); ++ } ++ } ++ ++ @Override ++ public double bc() { ++ return (double) this.getHeight() * 0.67D; ++ } ++ ++ @Override ++ public boolean er() { ++ return false; ++ } ++ ++ @Override ++ public boolean k(ItemStack itemstack) { ++ return EntityLlama.bw.test(itemstack); ++ } ++ ++ @Override ++ protected boolean c(EntityHuman entityhuman, ItemStack itemstack) { ++ byte b0 = 0; ++ byte b1 = 0; ++ float f = 0.0F; ++ boolean flag = false; ++ Item item = itemstack.getItem(); ++ ++ if (item == Items.WHEAT) { ++ b0 = 10; ++ b1 = 3; ++ f = 2.0F; ++ } else if (item == Blocks.HAY_BLOCK.getItem()) { ++ b0 = 90; ++ b1 = 6; ++ f = 10.0F; ++ if (this.isTamed() && this.getAge() == 0 && this.eP()) { ++ flag = true; ++ this.g(entityhuman); ++ } ++ } ++ ++ if (this.getHealth() < this.getMaxHealth() && f > 0.0F) { ++ this.heal(f); ++ flag = true; ++ } ++ ++ if (this.isBaby() && b0 > 0) { ++ this.world.addParticle(Particles.HAPPY_VILLAGER, this.d(1.0D), this.cF() + 0.5D, this.g(1.0D), 0.0D, 0.0D, 0.0D); ++ if (!this.world.isClientSide) { ++ this.setAge(b0); ++ } ++ ++ flag = true; ++ } ++ ++ if (b1 > 0 && (flag || !this.isTamed()) && this.getTemper() < this.getMaxDomestication()) { ++ flag = true; ++ if (!this.world.isClientSide) { ++ this.v(b1); ++ } ++ } ++ ++ if (flag && !this.isSilent()) { ++ SoundEffect soundeffect = this.fg(); ++ ++ if (soundeffect != null) { ++ this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), this.fg(), this.getSoundCategory(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F); ++ } ++ } ++ ++ return flag; ++ } ++ ++ @Override ++ protected boolean isFrozen() { ++ return this.dl() || this.eZ(); ++ } ++ ++ @Nullable ++ @Override ++ public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @Nullable GroupDataEntity groupdataentity, @Nullable NBTTagCompound nbttagcompound) { ++ this.fE(); ++ int i; ++ ++ if (groupdataentity instanceof EntityLlama.b) { ++ i = ((EntityLlama.b) groupdataentity).a; ++ } else { ++ i = this.random.nextInt(4); ++ groupdataentity = new EntityLlama.b(i); ++ } ++ ++ this.setVariant(i); ++ return super.prepare(worldaccess, difficultydamagescaler, enummobspawn, (GroupDataEntity) groupdataentity, nbttagcompound); ++ } ++ ++ @Override ++ protected SoundEffect getSoundAngry() { ++ return SoundEffects.ENTITY_LLAMA_ANGRY; ++ } ++ ++ @Override ++ protected SoundEffect getSoundAmbient() { ++ return SoundEffects.ENTITY_LLAMA_AMBIENT; ++ } ++ ++ @Override ++ protected SoundEffect getSoundHurt(DamageSource damagesource) { ++ return SoundEffects.ENTITY_LLAMA_HURT; ++ } ++ ++ @Override ++ protected SoundEffect getSoundDeath() { ++ return SoundEffects.ENTITY_LLAMA_DEATH; ++ } ++ ++ @Nullable ++ @Override ++ protected SoundEffect fg() { ++ return SoundEffects.ENTITY_LLAMA_EAT; ++ } ++ ++ @Override ++ protected void b(BlockPosition blockposition, IBlockData iblockdata) { ++ this.playSound(SoundEffects.ENTITY_LLAMA_STEP, 0.15F, 1.0F); ++ } ++ ++ @Override ++ protected void eO() { ++ this.playSound(SoundEffects.ENTITY_LLAMA_CHEST, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); ++ } ++ ++ @Override ++ public void fm() { ++ SoundEffect soundeffect = this.getSoundAngry(); ++ ++ if (soundeffect != null) { ++ this.playSound(soundeffect, this.getSoundVolume(), this.dH()); ++ } ++ ++ } ++ ++ @Override ++ public int eU() { ++ return this.getStrength(); ++ } ++ ++ @Override ++ public boolean fs() { ++ return true; ++ } ++ ++ @Override ++ public boolean ft() { ++ return !this.inventoryChest.getItem(1).isEmpty(); ++ } ++ ++ @Override ++ public boolean l(ItemStack itemstack) { ++ Item item = itemstack.getItem(); ++ ++ return TagsItem.CARPETS.isTagged(item); ++ } ++ ++ @Override ++ public boolean canSaddle() { ++ return false; ++ } ++ ++ @Override ++ public void a(IInventory iinventory) { ++ EnumColor enumcolor = this.fy(); ++ ++ super.a(iinventory); ++ EnumColor enumcolor1 = this.fy(); ++ ++ if (this.ticksLived > 20 && enumcolor1 != null && enumcolor1 != enumcolor) { ++ this.playSound(SoundEffects.ENTITY_LLAMA_SWAG, 0.5F, 1.0F); ++ } ++ ++ } ++ ++ @Override ++ protected void fe() { ++ if (!this.world.isClientSide) { ++ super.fe(); ++ this.a(m(this.inventoryChest.getItem(1))); ++ } ++ } ++ ++ private void a(@Nullable EnumColor enumcolor) { ++ this.datawatcher.set(EntityLlama.by, enumcolor == null ? -1 : enumcolor.getColorIndex()); ++ } ++ ++ @Nullable ++ private static EnumColor m(ItemStack itemstack) { ++ Block block = Block.asBlock(itemstack.getItem()); ++ ++ return block instanceof BlockCarpet ? ((BlockCarpet) block).c() : null; ++ } ++ ++ @Nullable ++ public EnumColor fy() { ++ int i = (Integer) this.datawatcher.get(EntityLlama.by); ++ ++ return i == -1 ? null : EnumColor.fromColorIndex(i); ++ } ++ ++ @Override ++ public int getMaxDomestication() { ++ return 30; ++ } ++ ++ @Override ++ public boolean mate(EntityAnimal entityanimal) { ++ return entityanimal != this && entityanimal instanceof EntityLlama && this.fo() && ((EntityLlama) entityanimal).fo(); ++ } ++ ++ @Override ++ public EntityLlama createChild(WorldServer worldserver, EntityAgeable entityageable) { ++ EntityLlama entityllama = this.fz(); ++ ++ this.a(entityageable, (EntityHorseAbstract) entityllama); ++ EntityLlama entityllama1 = (EntityLlama) entityageable; ++ int i = this.random.nextInt(Math.max(this.getStrength(), entityllama1.getStrength())) + 1; ++ ++ if (this.random.nextFloat() < 0.03F) { ++ ++i; ++ } ++ ++ entityllama.setStrength(i); ++ entityllama.setVariant(this.random.nextBoolean() ? this.getVariant() : entityllama1.getVariant()); ++ return entityllama; ++ } ++ ++ protected EntityLlama fz() { ++ return (EntityLlama) EntityTypes.LLAMA.a(this.world); ++ } ++ ++ private void i(EntityLiving entityliving) { ++ EntityLlamaSpit entityllamaspit = new EntityLlamaSpit(this.world, this); ++ double d0 = entityliving.locX() - this.locX(); ++ double d1 = entityliving.e(0.3333333333333333D) - entityllamaspit.locY(); ++ double d2 = entityliving.locZ() - this.locZ(); ++ float f = MathHelper.sqrt(d0 * d0 + d2 * d2) * 0.2F; ++ ++ entityllamaspit.shoot(d0, d1 + (double) f, d2, 1.5F, 10.0F); ++ if (!this.isSilent()) { ++ this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_LLAMA_SPIT, this.getSoundCategory(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F); ++ } ++ ++ this.world.addEntity(entityllamaspit); ++ this.bA = true; ++ } ++ ++ private void A(boolean flag) { ++ this.bA = flag; ++ } ++ ++ @Override ++ public boolean b(float f, float f1) { ++ int i = this.e(f, f1); ++ ++ if (i <= 0) { ++ return false; ++ } else { ++ if (f >= 6.0F) { ++ this.damageEntity(DamageSource.FALL, (float) i); ++ if (this.isVehicle()) { ++ Iterator iterator = this.getAllPassengers().iterator(); ++ ++ while (iterator.hasNext()) { ++ Entity entity = (Entity) iterator.next(); ++ ++ entity.damageEntity(DamageSource.FALL, (float) i); ++ } ++ } ++ } ++ ++ this.playBlockStepSound(); ++ return true; ++ } ++ } ++ ++ public void fA() { ++ if (this.bB != null) { ++ this.bB.bC = null; ++ } ++ ++ this.bB = null; ++ } ++ ++ public void a(EntityLlama entityllama) { ++ this.bB = entityllama; ++ this.bB.bC = this; ++ } ++ ++ public boolean fB() { ++ return this.bC != null; ++ } ++ ++ public boolean fC() { ++ return this.bB != null; ++ } ++ ++ @Nullable ++ public EntityLlama fD() { ++ return this.bB; ++ } ++ ++ @Override ++ protected double eJ() { ++ return 2.0D; ++ } ++ ++ @Override ++ protected void fk() { ++ if (!this.fC() && this.isBaby()) { ++ super.fk(); ++ } ++ ++ } ++ ++ @Override ++ public boolean fl() { ++ return false; ++ } ++ ++ @Override ++ public void a(EntityLiving entityliving, float f) { ++ this.i(entityliving); ++ } ++ ++ static class a extends PathfinderGoalNearestAttackableTarget { ++ ++ public a(EntityLlama entityllama) { ++ super(entityllama, EntityWolf.class, 16, false, true, (entityliving) -> { ++ return !((EntityWolf) entityliving).isTamed(); ++ }); ++ } ++ ++ @Override ++ protected double k() { ++ return super.k() * 0.25D; ++ } ++ } ++ ++ static class c extends PathfinderGoalHurtByTarget { ++ ++ public c(EntityLlama entityllama) { ++ super(entityllama); ++ } ++ ++ @Override ++ public boolean b() { ++ if (this.e instanceof EntityLlama) { ++ EntityLlama entityllama = (EntityLlama) this.e; ++ ++ if (entityllama.bA) { ++ entityllama.A(false); ++ return false; ++ } ++ } ++ ++ return super.b(); ++ } ++ } ++ ++ static class b extends EntityAgeable.a { ++ ++ public final int a; ++ ++ private b(int i) { ++ super(true); ++ this.a = i; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerLandedFlame.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerLandedFlame.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fbecb61b8c511fc7daa21690b2a653254be74246 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerLandedFlame.java +@@ -0,0 +1,111 @@ ++package net.minecraft.world.entity.boss.enderdragon.phases; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.particles.Particles; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.effect.MobEffect; ++import net.minecraft.world.effect.MobEffects; ++import net.minecraft.world.entity.EntityAreaEffectCloud; ++import net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon; ++import net.minecraft.world.phys.Vec3D; ++ ++public class DragonControllerLandedFlame extends AbstractDragonControllerLanded { ++ ++ private int b; ++ private int c; ++ private EntityAreaEffectCloud d; ++ ++ public DragonControllerLandedFlame(EntityEnderDragon entityenderdragon) { ++ super(entityenderdragon); ++ } ++ ++ @Override ++ public void b() { ++ ++this.b; ++ if (this.b % 2 == 0 && this.b < 10) { ++ Vec3D vec3d = this.a.x(1.0F).d(); ++ ++ vec3d.b(-0.7853982F); ++ double d0 = this.a.bo.locX(); ++ double d1 = this.a.bo.e(0.5D); ++ double d2 = this.a.bo.locZ(); ++ ++ for (int i = 0; i < 8; ++i) { ++ double d3 = d0 + this.a.getRandom().nextGaussian() / 2.0D; ++ double d4 = d1 + this.a.getRandom().nextGaussian() / 2.0D; ++ double d5 = d2 + this.a.getRandom().nextGaussian() / 2.0D; ++ ++ for (int j = 0; j < 6; ++j) { ++ this.a.world.addParticle(Particles.DRAGON_BREATH, d3, d4, d5, -vec3d.x * 0.07999999821186066D * (double) j, -vec3d.y * 0.6000000238418579D, -vec3d.z * 0.07999999821186066D * (double) j); ++ } ++ ++ vec3d.b(0.19634955F); ++ } ++ } ++ ++ } ++ ++ @Override ++ public void c() { ++ ++this.b; ++ if (this.b >= 200) { ++ if (this.c >= 4) { ++ this.a.getDragonControllerManager().setControllerPhase(DragonControllerPhase.TAKEOFF); ++ } else { ++ this.a.getDragonControllerManager().setControllerPhase(DragonControllerPhase.SITTING_SCANNING); ++ } ++ } else if (this.b == 10) { ++ Vec3D vec3d = (new Vec3D(this.a.bo.locX() - this.a.locX(), 0.0D, this.a.bo.locZ() - this.a.locZ())).d(); ++ float f = 5.0F; ++ double d0 = this.a.bo.locX() + vec3d.x * 5.0D / 2.0D; ++ double d1 = this.a.bo.locZ() + vec3d.z * 5.0D / 2.0D; ++ double d2 = this.a.bo.e(0.5D); ++ double d3 = d2; ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(d0, d2, d1); ++ ++ while (this.a.world.isEmpty(blockposition_mutableblockposition)) { ++ --d3; ++ if (d3 < 0.0D) { ++ d3 = d2; ++ break; ++ } ++ ++ blockposition_mutableblockposition.c(d0, d3, d1); ++ } ++ ++ d3 = (double) (MathHelper.floor(d3) + 1); ++ this.d = new EntityAreaEffectCloud(this.a.world, d0, d3, d1); ++ this.d.setSource(this.a); ++ this.d.setRadius(5.0F); ++ this.d.setDuration(200); ++ this.d.setParticle(Particles.DRAGON_BREATH); ++ this.d.addEffect(new MobEffect(MobEffects.HARM)); ++ this.a.world.addEntity(this.d); ++ } ++ ++ } ++ ++ @Override ++ public void d() { ++ this.b = 0; ++ ++this.c; ++ } ++ ++ @Override ++ public void e() { ++ if (this.d != null) { ++ this.d.die(); ++ this.d = null; ++ } ++ ++ } ++ ++ @Override ++ public DragonControllerPhase getControllerPhase() { ++ return DragonControllerPhase.SITTING_FLAMING; ++ } ++ ++ public void j() { ++ this.c = 0; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerStrafe.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerStrafe.java +new file mode 100644 +index 0000000000000000000000000000000000000000..db3cef26c8d5cdf740bb151a5525d8740a0e8bbd +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerStrafe.java +@@ -0,0 +1,198 @@ ++package net.minecraft.world.entity.boss.enderdragon.phases; ++ ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.projectile.EntityDragonFireball; ++import net.minecraft.world.level.pathfinder.PathEntity; ++import net.minecraft.world.level.pathfinder.PathPoint; ++import net.minecraft.world.phys.Vec3D; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class DragonControllerStrafe extends AbstractDragonController { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private int c; ++ private PathEntity d; ++ private Vec3D e; ++ private EntityLiving f; ++ private boolean g; ++ ++ public DragonControllerStrafe(EntityEnderDragon entityenderdragon) { ++ super(entityenderdragon); ++ } ++ ++ @Override ++ public void c() { ++ if (this.f == null) { ++ DragonControllerStrafe.LOGGER.warn("Skipping player strafe phase because no player was found"); ++ this.a.getDragonControllerManager().setControllerPhase(DragonControllerPhase.HOLDING_PATTERN); ++ } else { ++ double d0; ++ double d1; ++ double d2; ++ ++ if (this.d != null && this.d.c()) { ++ d0 = this.f.locX(); ++ d1 = this.f.locZ(); ++ double d3 = d0 - this.a.locX(); ++ double d4 = d1 - this.a.locZ(); ++ ++ d2 = (double) MathHelper.sqrt(d3 * d3 + d4 * d4); ++ double d5 = Math.min(0.4000000059604645D + d2 / 80.0D - 1.0D, 10.0D); ++ ++ this.e = new Vec3D(d0, this.f.locY() + d5, d1); ++ } ++ ++ d0 = this.e == null ? 0.0D : this.e.c(this.a.locX(), this.a.locY(), this.a.locZ()); ++ if (d0 < 100.0D || d0 > 22500.0D) { ++ this.j(); ++ } ++ ++ d1 = 64.0D; ++ if (this.f.h((Entity) this.a) < 4096.0D) { ++ if (this.a.hasLineOfSight(this.f)) { ++ ++this.c; ++ Vec3D vec3d = (new Vec3D(this.f.locX() - this.a.locX(), 0.0D, this.f.locZ() - this.a.locZ())).d(); ++ Vec3D vec3d1 = (new Vec3D((double) MathHelper.sin(this.a.yaw * 0.017453292F), 0.0D, (double) (-MathHelper.cos(this.a.yaw * 0.017453292F)))).d(); ++ float f = (float) vec3d1.b(vec3d); ++ float f1 = (float) (Math.acos((double) f) * 57.2957763671875D); ++ ++ f1 += 0.5F; ++ if (this.c >= 5 && f1 >= 0.0F && f1 < 10.0F) { ++ d2 = 1.0D; ++ Vec3D vec3d2 = this.a.f(1.0F); ++ double d6 = this.a.bo.locX() - vec3d2.x * 1.0D; ++ double d7 = this.a.bo.e(0.5D) + 0.5D; ++ double d8 = this.a.bo.locZ() - vec3d2.z * 1.0D; ++ double d9 = this.f.locX() - d6; ++ double d10 = this.f.e(0.5D) - d7; ++ double d11 = this.f.locZ() - d8; ++ ++ if (!this.a.isSilent()) { ++ this.a.world.a((EntityHuman) null, 1017, this.a.getChunkCoordinates(), 0); ++ } ++ ++ EntityDragonFireball entitydragonfireball = new EntityDragonFireball(this.a.world, this.a, d9, d10, d11); ++ ++ entitydragonfireball.setPositionRotation(d6, d7, d8, 0.0F, 0.0F); ++ this.a.world.addEntity(entitydragonfireball); ++ this.c = 0; ++ if (this.d != null) { ++ while (!this.d.c()) { ++ this.d.a(); ++ } ++ } ++ ++ this.a.getDragonControllerManager().setControllerPhase(DragonControllerPhase.HOLDING_PATTERN); ++ } ++ } else if (this.c > 0) { ++ --this.c; ++ } ++ } else if (this.c > 0) { ++ --this.c; ++ } ++ ++ } ++ } ++ ++ private void j() { ++ if (this.d == null || this.d.c()) { ++ int i = this.a.eI(); ++ int j = i; ++ ++ if (this.a.getRandom().nextInt(8) == 0) { ++ this.g = !this.g; ++ j = i + 6; ++ } ++ ++ if (this.g) { ++ ++j; ++ } else { ++ --j; ++ } ++ ++ if (this.a.getEnderDragonBattle() != null && this.a.getEnderDragonBattle().c() > 0) { ++ j %= 12; ++ if (j < 0) { ++ j += 12; ++ } ++ } else { ++ j -= 12; ++ j &= 7; ++ j += 12; ++ } ++ ++ this.d = this.a.a(i, j, (PathPoint) null); ++ if (this.d != null) { ++ this.d.a(); ++ } ++ } ++ ++ this.k(); ++ } ++ ++ private void k() { ++ if (this.d != null && !this.d.c()) { ++ BlockPosition blockposition = this.d.g(); ++ ++ this.d.a(); ++ double d0 = (double) blockposition.getX(); ++ double d1 = (double) blockposition.getZ(); ++ ++ double d2; ++ ++ do { ++ d2 = (double) ((float) blockposition.getY() + this.a.getRandom().nextFloat() * 20.0F); ++ } while (d2 < (double) blockposition.getY()); ++ ++ this.e = new Vec3D(d0, d2, d1); ++ } ++ ++ } ++ ++ @Override ++ public void d() { ++ this.c = 0; ++ this.e = null; ++ this.d = null; ++ this.f = null; ++ } ++ ++ public void a(EntityLiving entityliving) { ++ this.f = entityliving; ++ int i = this.a.eI(); ++ int j = this.a.p(this.f.locX(), this.f.locY(), this.f.locZ()); ++ int k = MathHelper.floor(this.f.locX()); ++ int l = MathHelper.floor(this.f.locZ()); ++ double d0 = (double) k - this.a.locX(); ++ double d1 = (double) l - this.a.locZ(); ++ double d2 = (double) MathHelper.sqrt(d0 * d0 + d1 * d1); ++ double d3 = Math.min(0.4000000059604645D + d2 / 80.0D - 1.0D, 10.0D); ++ int i1 = MathHelper.floor(this.f.locY() + d3); ++ PathPoint pathpoint = new PathPoint(k, i1, l); ++ ++ this.d = this.a.a(i, j, pathpoint); ++ if (this.d != null) { ++ this.d.a(); ++ this.k(); ++ } ++ ++ } ++ ++ @Nullable ++ @Override ++ public Vec3D g() { ++ return this.e; ++ } ++ ++ @Override ++ public DragonControllerPhase getControllerPhase() { ++ return DragonControllerPhase.STRAFE_PLAYER; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityDrowned.java b/src/main/java/net/minecraft/world/entity/monster/EntityDrowned.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c3457fc7c0f72af79b12dc50c270ca24b7590c22 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityDrowned.java +@@ -0,0 +1,505 @@ ++package net.minecraft.world.entity.monster; ++ ++import java.util.EnumSet; ++import java.util.Objects; ++import java.util.Optional; ++import java.util.Random; ++import javax.annotation.Nullable; ++import net.minecraft.core.BaseBlockPosition; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.sounds.SoundEffect; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsFluid; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.DifficultyDamageScaler; ++import net.minecraft.world.EnumDifficulty; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityCreature; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.EnumItemSlot; ++import net.minecraft.world.entity.EnumMobSpawn; ++import net.minecraft.world.entity.EnumMoveType; ++import net.minecraft.world.entity.GroupDataEntity; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; ++import net.minecraft.world.entity.ai.control.ControllerMove; ++import net.minecraft.world.entity.ai.goal.PathfinderGoal; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalArrowAttack; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalGotoTarget; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalRandomStroll; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalZombieAttack; ++import net.minecraft.world.entity.ai.goal.target.PathfinderGoalHurtByTarget; ++import net.minecraft.world.entity.ai.goal.target.PathfinderGoalNearestAttackableTarget; ++import net.minecraft.world.entity.ai.navigation.Navigation; ++import net.minecraft.world.entity.ai.navigation.NavigationGuardian; ++import net.minecraft.world.entity.ai.util.RandomPositionGenerator; ++import net.minecraft.world.entity.animal.EntityIronGolem; ++import net.minecraft.world.entity.animal.EntityTurtle; ++import net.minecraft.world.entity.npc.EntityVillagerAbstract; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.projectile.EntityThrownTrident; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.level.GeneratorAccess; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.IWorldReader; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.WorldAccess; ++import net.minecraft.world.level.biome.BiomeBase; ++import net.minecraft.world.level.biome.Biomes; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.pathfinder.PathEntity; ++import net.minecraft.world.level.pathfinder.PathType; ++import net.minecraft.world.phys.Vec3D; ++ ++public class EntityDrowned extends EntityZombie implements IRangedEntity { ++ ++ private boolean d; ++ public final NavigationGuardian navigationWater; ++ public final Navigation navigationLand; ++ ++ public EntityDrowned(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ this.G = 1.0F; ++ this.moveController = new EntityDrowned.d(this); ++ this.a(PathType.WATER, 0.0F); ++ this.navigationWater = new NavigationGuardian(this, world); ++ this.navigationLand = new Navigation(this, world); ++ } ++ ++ @Override ++ protected void m() { ++ this.goalSelector.a(1, new EntityDrowned.c(this, 1.0D)); ++ this.goalSelector.a(2, new EntityDrowned.f(this, 1.0D, 40, 10.0F)); ++ this.goalSelector.a(2, new EntityDrowned.a(this, 1.0D, false)); ++ this.goalSelector.a(5, new EntityDrowned.b(this, 1.0D)); ++ this.goalSelector.a(6, new EntityDrowned.e(this, 1.0D, this.world.getSeaLevel())); ++ this.goalSelector.a(7, new PathfinderGoalRandomStroll(this, 1.0D)); ++ this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityDrowned.class})).a(EntityPigZombie.class)); ++ this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, this::i)); ++ this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)); ++ this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, true)); ++ this.targetSelector.a(5, new PathfinderGoalNearestAttackableTarget<>(this, EntityTurtle.class, 10, true, false, EntityTurtle.bo)); ++ } ++ ++ @Override ++ public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @Nullable GroupDataEntity groupdataentity, @Nullable NBTTagCompound nbttagcompound) { ++ groupdataentity = super.prepare(worldaccess, difficultydamagescaler, enummobspawn, groupdataentity, nbttagcompound); ++ if (this.getEquipment(EnumItemSlot.OFFHAND).isEmpty() && this.random.nextFloat() < 0.03F) { ++ this.setSlot(EnumItemSlot.OFFHAND, new ItemStack(Items.NAUTILUS_SHELL)); ++ this.dropChanceHand[EnumItemSlot.OFFHAND.b()] = 2.0F; ++ } ++ ++ return groupdataentity; ++ } ++ ++ public static boolean a(EntityTypes entitytypes, WorldAccess worldaccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) { ++ Optional> optional = worldaccess.i(blockposition); ++ boolean flag = worldaccess.getDifficulty() != EnumDifficulty.PEACEFUL && a(worldaccess, blockposition, random) && (enummobspawn == EnumMobSpawn.SPAWNER || worldaccess.getFluid(blockposition).a((Tag) TagsFluid.WATER)); ++ ++ return !Objects.equals(optional, Optional.of(Biomes.RIVER)) && !Objects.equals(optional, Optional.of(Biomes.FROZEN_RIVER)) ? random.nextInt(40) == 0 && a((GeneratorAccess) worldaccess, blockposition) && flag : random.nextInt(15) == 0 && flag; ++ } ++ ++ private static boolean a(GeneratorAccess generatoraccess, BlockPosition blockposition) { ++ return blockposition.getY() < generatoraccess.getSeaLevel() - 5; ++ } ++ ++ @Override ++ protected boolean eK() { ++ return false; ++ } ++ ++ @Override ++ protected SoundEffect getSoundAmbient() { ++ return this.isInWater() ? SoundEffects.ENTITY_DROWNED_AMBIENT_WATER : SoundEffects.ENTITY_DROWNED_AMBIENT; ++ } ++ ++ @Override ++ protected SoundEffect getSoundHurt(DamageSource damagesource) { ++ return this.isInWater() ? SoundEffects.ENTITY_DROWNED_HURT_WATER : SoundEffects.ENTITY_DROWNED_HURT; ++ } ++ ++ @Override ++ protected SoundEffect getSoundDeath() { ++ return this.isInWater() ? SoundEffects.ENTITY_DROWNED_DEATH_WATER : SoundEffects.ENTITY_DROWNED_DEATH; ++ } ++ ++ @Override ++ protected SoundEffect getSoundStep() { ++ return SoundEffects.ENTITY_DROWNED_STEP; ++ } ++ ++ @Override ++ protected SoundEffect getSoundSwim() { ++ return SoundEffects.ENTITY_DROWNED_SWIM; ++ } ++ ++ @Override ++ protected ItemStack eM() { ++ return ItemStack.b; ++ } ++ ++ @Override ++ protected void a(DifficultyDamageScaler difficultydamagescaler) { ++ if ((double) this.random.nextFloat() > 0.9D) { ++ int i = this.random.nextInt(16); ++ ++ if (i < 10) { ++ this.setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.TRIDENT)); ++ } else { ++ this.setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.FISHING_ROD)); ++ } ++ } ++ ++ } ++ ++ @Override ++ protected boolean a(ItemStack itemstack, ItemStack itemstack1) { ++ return itemstack1.getItem() == Items.NAUTILUS_SHELL ? false : (itemstack1.getItem() == Items.TRIDENT ? (itemstack.getItem() == Items.TRIDENT ? itemstack.getDamage() < itemstack1.getDamage() : false) : (itemstack.getItem() == Items.TRIDENT ? true : super.a(itemstack, itemstack1))); ++ } ++ ++ @Override ++ protected boolean eN() { ++ return false; ++ } ++ ++ @Override ++ public boolean a(IWorldReader iworldreader) { ++ return iworldreader.j((Entity) this); ++ } ++ ++ public boolean i(@Nullable EntityLiving entityliving) { ++ return entityliving != null ? !this.world.isDay() || entityliving.isInWater() : false; ++ } ++ ++ @Override ++ public boolean bV() { ++ return !this.isSwimming(); ++ } ++ ++ private boolean eW() { ++ if (this.d) { ++ return true; ++ } else { ++ EntityLiving entityliving = this.getGoalTarget(); ++ ++ return entityliving != null && entityliving.isInWater(); ++ } ++ } ++ ++ @Override ++ public void g(Vec3D vec3d) { ++ if (this.doAITick() && this.isInWater() && this.eW()) { ++ this.a(0.01F, vec3d); ++ this.move(EnumMoveType.SELF, this.getMot()); ++ this.setMot(this.getMot().a(0.9D)); ++ } else { ++ super.g(vec3d); ++ } ++ ++ } ++ ++ @Override ++ public void aJ() { ++ if (!this.world.isClientSide) { ++ if (this.doAITick() && this.isInWater() && this.eW()) { ++ this.navigation = this.navigationWater; ++ this.setSwimming(true); ++ } else { ++ this.navigation = this.navigationLand; ++ this.setSwimming(false); ++ } ++ } ++ ++ } ++ ++ protected boolean eO() { ++ PathEntity pathentity = this.getNavigation().k(); ++ ++ if (pathentity != null) { ++ BlockPosition blockposition = pathentity.m(); ++ ++ if (blockposition != null) { ++ double d0 = this.h((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()); ++ ++ if (d0 < 4.0D) { ++ return true; ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public void a(EntityLiving entityliving, float f) { ++ EntityThrownTrident entitythrowntrident = new EntityThrownTrident(this.world, this, new ItemStack(Items.TRIDENT)); ++ double d0 = entityliving.locX() - this.locX(); ++ double d1 = entityliving.e(0.3333333333333333D) - entitythrowntrident.locY(); ++ double d2 = entityliving.locZ() - this.locZ(); ++ double d3 = (double) MathHelper.sqrt(d0 * d0 + d2 * d2); ++ ++ entitythrowntrident.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.world.getDifficulty().a() * 4)); ++ this.playSound(SoundEffects.ENTITY_DROWNED_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); ++ this.world.addEntity(entitythrowntrident); ++ } ++ ++ public void t(boolean flag) { ++ this.d = flag; ++ } ++ ++ static class d extends ControllerMove { ++ ++ private final EntityDrowned i; ++ ++ public d(EntityDrowned entitydrowned) { ++ super(entitydrowned); ++ this.i = entitydrowned; ++ } ++ ++ @Override ++ public void a() { ++ EntityLiving entityliving = this.i.getGoalTarget(); ++ ++ if (this.i.eW() && this.i.isInWater()) { ++ if (entityliving != null && entityliving.locY() > this.i.locY() || this.i.d) { ++ this.i.setMot(this.i.getMot().add(0.0D, 0.002D, 0.0D)); ++ } ++ ++ if (this.h != ControllerMove.Operation.MOVE_TO || this.i.getNavigation().m()) { ++ this.i.q(0.0F); ++ return; ++ } ++ ++ double d0 = this.b - this.i.locX(); ++ double d1 = this.c - this.i.locY(); ++ double d2 = this.d - this.i.locZ(); ++ double d3 = (double) MathHelper.sqrt(d0 * d0 + d1 * d1 + d2 * d2); ++ ++ d1 /= d3; ++ float f = (float) (MathHelper.d(d2, d0) * 57.2957763671875D) - 90.0F; ++ ++ this.i.yaw = this.a(this.i.yaw, f, 90.0F); ++ this.i.aA = this.i.yaw; ++ float f1 = (float) (this.e * this.i.b(GenericAttributes.MOVEMENT_SPEED)); ++ float f2 = MathHelper.g(0.125F, this.i.dN(), f1); ++ ++ this.i.q(f2); ++ this.i.setMot(this.i.getMot().add((double) f2 * d0 * 0.005D, (double) f2 * d1 * 0.1D, (double) f2 * d2 * 0.005D)); ++ } else { ++ if (!this.i.onGround) { ++ this.i.setMot(this.i.getMot().add(0.0D, -0.008D, 0.0D)); ++ } ++ ++ super.a(); ++ } ++ ++ } ++ } ++ ++ static class a extends PathfinderGoalZombieAttack { ++ ++ private final EntityDrowned b; ++ ++ public a(EntityDrowned entitydrowned, double d0, boolean flag) { ++ super((EntityZombie) entitydrowned, d0, flag); ++ this.b = entitydrowned; ++ } ++ ++ @Override ++ public boolean a() { ++ return super.a() && this.b.i(this.b.getGoalTarget()); ++ } ++ ++ @Override ++ public boolean b() { ++ return super.b() && this.b.i(this.b.getGoalTarget()); ++ } ++ } ++ ++ static class c extends PathfinderGoal { ++ ++ private final EntityCreature a; ++ private double b; ++ private double c; ++ private double d; ++ private final double e; ++ private final World f; ++ ++ public c(EntityCreature entitycreature, double d0) { ++ this.a = entitycreature; ++ this.e = d0; ++ this.f = entitycreature.world; ++ this.a(EnumSet.of(PathfinderGoal.Type.MOVE)); ++ } ++ ++ @Override ++ public boolean a() { ++ if (!this.f.isDay()) { ++ return false; ++ } else if (this.a.isInWater()) { ++ return false; ++ } else { ++ Vec3D vec3d = this.g(); ++ ++ if (vec3d == null) { ++ return false; ++ } else { ++ this.b = vec3d.x; ++ this.c = vec3d.y; ++ this.d = vec3d.z; ++ return true; ++ } ++ } ++ } ++ ++ @Override ++ public boolean b() { ++ return !this.a.getNavigation().m(); ++ } ++ ++ @Override ++ public void c() { ++ this.a.getNavigation().a(this.b, this.c, this.d, this.e); ++ } ++ ++ @Nullable ++ private Vec3D g() { ++ Random random = this.a.getRandom(); ++ BlockPosition blockposition = this.a.getChunkCoordinates(); ++ ++ for (int i = 0; i < 10; ++i) { ++ BlockPosition blockposition1 = blockposition.b(random.nextInt(20) - 10, 2 - random.nextInt(8), random.nextInt(20) - 10); ++ ++ if (this.f.getType(blockposition1).a(Blocks.WATER)) { ++ return Vec3D.c((BaseBlockPosition) blockposition1); ++ } ++ } ++ ++ return null; ++ } ++ } ++ ++ static class b extends PathfinderGoalGotoTarget { ++ ++ private final EntityDrowned g; ++ ++ public b(EntityDrowned entitydrowned, double d0) { ++ super(entitydrowned, d0, 8, 2); ++ this.g = entitydrowned; ++ } ++ ++ @Override ++ public boolean a() { ++ return super.a() && !this.g.world.isDay() && this.g.isInWater() && this.g.locY() >= (double) (this.g.world.getSeaLevel() - 3); ++ } ++ ++ @Override ++ public boolean b() { ++ return super.b(); ++ } ++ ++ @Override ++ protected boolean a(IWorldReader iworldreader, BlockPosition blockposition) { ++ BlockPosition blockposition1 = blockposition.up(); ++ ++ return iworldreader.isEmpty(blockposition1) && iworldreader.isEmpty(blockposition1.up()) ? iworldreader.getType(blockposition).a((IBlockAccess) iworldreader, blockposition, (Entity) this.g) : false; ++ } ++ ++ @Override ++ public void c() { ++ this.g.t(false); ++ this.g.navigation = this.g.navigationLand; ++ super.c(); ++ } ++ ++ @Override ++ public void d() { ++ super.d(); ++ } ++ } ++ ++ static class e extends PathfinderGoal { ++ ++ private final EntityDrowned a; ++ private final double b; ++ private final int c; ++ private boolean d; ++ ++ public e(EntityDrowned entitydrowned, double d0, int i) { ++ this.a = entitydrowned; ++ this.b = d0; ++ this.c = i; ++ } ++ ++ @Override ++ public boolean a() { ++ return !this.a.world.isDay() && this.a.isInWater() && this.a.locY() < (double) (this.c - 2); ++ } ++ ++ @Override ++ public boolean b() { ++ return this.a() && !this.d; ++ } ++ ++ @Override ++ public void e() { ++ if (this.a.locY() < (double) (this.c - 1) && (this.a.getNavigation().m() || this.a.eO())) { ++ Vec3D vec3d = RandomPositionGenerator.b(this.a, 4, 8, new Vec3D(this.a.locX(), (double) (this.c - 1), this.a.locZ())); ++ ++ if (vec3d == null) { ++ this.d = true; ++ return; ++ } ++ ++ this.a.getNavigation().a(vec3d.x, vec3d.y, vec3d.z, this.b); ++ } ++ ++ } ++ ++ @Override ++ public void c() { ++ this.a.t(true); ++ this.d = false; ++ } ++ ++ @Override ++ public void d() { ++ this.a.t(false); ++ } ++ } ++ ++ static class f extends PathfinderGoalArrowAttack { ++ ++ private final EntityDrowned a; ++ ++ public f(IRangedEntity irangedentity, double d0, int i, float f) { ++ super(irangedentity, d0, i, f); ++ this.a = (EntityDrowned) irangedentity; ++ } ++ ++ @Override ++ public boolean a() { ++ return super.a() && this.a.getItemInMainHand().getItem() == Items.TRIDENT; ++ } ++ ++ @Override ++ public void c() { ++ super.c(); ++ this.a.setAggressive(true); ++ this.a.c(EnumHand.MAIN_HAND); ++ } ++ ++ @Override ++ public void d() { ++ super.d(); ++ this.a.clearActiveItem(); ++ this.a.setAggressive(false); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityEvoker.java b/src/main/java/net/minecraft/world/entity/monster/EntityEvoker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..64bfd84e8a5a0c2b1fec7044b688c7cc1083f18a +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityEvoker.java +@@ -0,0 +1,378 @@ ++package net.minecraft.world.entity.monster; ++ ++import java.util.List; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.sounds.SoundEffect; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.EnumMobSpawn; ++import net.minecraft.world.entity.EnumMonsterType; ++import net.minecraft.world.entity.GroupDataEntity; ++import net.minecraft.world.entity.ai.attributes.AttributeProvider; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalAvoidTarget; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalFloat; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalLookAtPlayer; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalRandomStroll; ++import net.minecraft.world.entity.ai.goal.target.PathfinderGoalHurtByTarget; ++import net.minecraft.world.entity.ai.goal.target.PathfinderGoalNearestAttackableTarget; ++import net.minecraft.world.entity.ai.targeting.PathfinderTargetCondition; ++import net.minecraft.world.entity.animal.EntityIronGolem; ++import net.minecraft.world.entity.animal.EntitySheep; ++import net.minecraft.world.entity.npc.EntityVillagerAbstract; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.projectile.EntityEvokerFangs; ++import net.minecraft.world.entity.raid.EntityRaider; ++import net.minecraft.world.item.EnumColor; ++import net.minecraft.world.level.GameRules; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.phys.shapes.VoxelShape; ++ ++public class EntityEvoker extends EntityIllagerWizard { ++ ++ private EntitySheep bo; ++ ++ public EntityEvoker(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ this.f = 10; ++ } ++ ++ @Override ++ protected void initPathfinder() { ++ super.initPathfinder(); ++ this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(1, new EntityEvoker.b()); ++ this.goalSelector.a(2, new PathfinderGoalAvoidTarget<>(this, EntityHuman.class, 8.0F, 0.6D, 1.0D)); ++ this.goalSelector.a(4, new EntityEvoker.c()); ++ this.goalSelector.a(5, new EntityEvoker.a()); ++ this.goalSelector.a(6, new EntityEvoker.d()); ++ this.goalSelector.a(8, new PathfinderGoalRandomStroll(this, 0.6D)); ++ this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 3.0F, 1.0F)); ++ this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F)); ++ this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a()); ++ this.targetSelector.a(2, (new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)).a(300)); ++ this.targetSelector.a(3, (new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)).a(300)); ++ this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, false)); ++ } ++ ++ public static AttributeProvider.Builder eK() { ++ return EntityMonster.eR().a(GenericAttributes.MOVEMENT_SPEED, 0.5D).a(GenericAttributes.FOLLOW_RANGE, 12.0D).a(GenericAttributes.MAX_HEALTH, 24.0D); ++ } ++ ++ @Override ++ protected void initDatawatcher() { ++ super.initDatawatcher(); ++ } ++ ++ @Override ++ public void loadData(NBTTagCompound nbttagcompound) { ++ super.loadData(nbttagcompound); ++ } ++ ++ @Override ++ public SoundEffect eL() { ++ return SoundEffects.ENTITY_EVOKER_CELEBRATE; ++ } ++ ++ @Override ++ public void saveData(NBTTagCompound nbttagcompound) { ++ super.saveData(nbttagcompound); ++ } ++ ++ @Override ++ protected void mobTick() { ++ super.mobTick(); ++ } ++ ++ @Override ++ public boolean r(Entity entity) { ++ return entity == null ? false : (entity == this ? true : (super.r(entity) ? true : (entity instanceof EntityVex ? this.r(((EntityVex) entity).eK()) : (entity instanceof EntityLiving && ((EntityLiving) entity).getMonsterType() == EnumMonsterType.ILLAGER ? this.getScoreboardTeam() == null && entity.getScoreboardTeam() == null : false)))); ++ } ++ ++ @Override ++ protected SoundEffect getSoundAmbient() { ++ return SoundEffects.ENTITY_EVOKER_AMBIENT; ++ } ++ ++ @Override ++ protected SoundEffect getSoundDeath() { ++ return SoundEffects.ENTITY_EVOKER_DEATH; ++ } ++ ++ @Override ++ protected SoundEffect getSoundHurt(DamageSource damagesource) { ++ return SoundEffects.ENTITY_EVOKER_HURT; ++ } ++ ++ private void a(@Nullable EntitySheep entitysheep) { ++ this.bo = entitysheep; ++ } ++ ++ @Nullable ++ private EntitySheep fg() { ++ return this.bo; ++ } ++ ++ @Override ++ protected SoundEffect getSoundCastSpell() { ++ return SoundEffects.ENTITY_EVOKER_CAST_SPELL; ++ } ++ ++ @Override ++ public void a(int i, boolean flag) {} ++ ++ public class d extends EntityIllagerWizard.PathfinderGoalCastSpell { ++ ++ private final PathfinderTargetCondition e = (new PathfinderTargetCondition()).a(16.0D).a().a((entityliving) -> { ++ return ((EntitySheep) entityliving).getColor() == EnumColor.BLUE; ++ }); ++ ++ public d() { ++ super(); ++ } ++ ++ @Override ++ public boolean a() { ++ if (EntityEvoker.this.getGoalTarget() != null) { ++ return false; ++ } else if (EntityEvoker.this.eW()) { ++ return false; ++ } else if (EntityEvoker.this.ticksLived < this.c) { ++ return false; ++ } else if (!EntityEvoker.this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ return false; ++ } else { ++ List list = EntityEvoker.this.world.a(EntitySheep.class, this.e, EntityEvoker.this, EntityEvoker.this.getBoundingBox().grow(16.0D, 4.0D, 16.0D)); ++ ++ if (list.isEmpty()) { ++ return false; ++ } else { ++ EntityEvoker.this.a((EntitySheep) list.get(EntityEvoker.this.random.nextInt(list.size()))); ++ return true; ++ } ++ } ++ } ++ ++ @Override ++ public boolean b() { ++ return EntityEvoker.this.fg() != null && this.b > 0; ++ } ++ ++ @Override ++ public void d() { ++ super.d(); ++ EntityEvoker.this.a((EntitySheep) null); ++ } ++ ++ @Override ++ protected void j() { ++ EntitySheep entitysheep = EntityEvoker.this.fg(); ++ ++ if (entitysheep != null && entitysheep.isAlive()) { ++ entitysheep.setColor(EnumColor.RED); ++ } ++ ++ } ++ ++ @Override ++ protected int m() { ++ return 40; ++ } ++ ++ @Override ++ protected int g() { ++ return 60; ++ } ++ ++ @Override ++ protected int h() { ++ return 140; ++ } ++ ++ @Override ++ protected SoundEffect k() { ++ return SoundEffects.ENTITY_EVOKER_PREPARE_WOLOLO; ++ } ++ ++ @Override ++ protected EntityIllagerWizard.Spell getCastSpell() { ++ return EntityIllagerWizard.Spell.WOLOLO; ++ } ++ } ++ ++ class c extends EntityIllagerWizard.PathfinderGoalCastSpell { ++ ++ private final PathfinderTargetCondition e; ++ ++ private c() { ++ super(); ++ this.e = (new PathfinderTargetCondition()).a(16.0D).c().e().a().b(); ++ } ++ ++ @Override ++ public boolean a() { ++ if (!super.a()) { ++ return false; ++ } else { ++ int i = EntityEvoker.this.world.a(EntityVex.class, this.e, EntityEvoker.this, EntityEvoker.this.getBoundingBox().g(16.0D)).size(); ++ ++ return EntityEvoker.this.random.nextInt(8) + 1 > i; ++ } ++ } ++ ++ @Override ++ protected int g() { ++ return 100; ++ } ++ ++ @Override ++ protected int h() { ++ return 340; ++ } ++ ++ @Override ++ protected void j() { ++ WorldServer worldserver = (WorldServer) EntityEvoker.this.world; ++ ++ for (int i = 0; i < 3; ++i) { ++ BlockPosition blockposition = EntityEvoker.this.getChunkCoordinates().b(-2 + EntityEvoker.this.random.nextInt(5), 1, -2 + EntityEvoker.this.random.nextInt(5)); ++ EntityVex entityvex = (EntityVex) EntityTypes.VEX.a(EntityEvoker.this.world); ++ ++ entityvex.setPositionRotation(blockposition, 0.0F, 0.0F); ++ entityvex.prepare(worldserver, EntityEvoker.this.world.getDamageScaler(blockposition), EnumMobSpawn.MOB_SUMMONED, (GroupDataEntity) null, (NBTTagCompound) null); ++ entityvex.a((EntityInsentient) EntityEvoker.this); ++ entityvex.g(blockposition); ++ entityvex.a(20 * (30 + EntityEvoker.this.random.nextInt(90))); ++ worldserver.addAllEntities(entityvex); ++ } ++ ++ } ++ ++ @Override ++ protected SoundEffect k() { ++ return SoundEffects.ENTITY_EVOKER_PREPARE_SUMMON; ++ } ++ ++ @Override ++ protected EntityIllagerWizard.Spell getCastSpell() { ++ return EntityIllagerWizard.Spell.SUMMON_VEX; ++ } ++ } ++ ++ class a extends EntityIllagerWizard.PathfinderGoalCastSpell { ++ ++ private a() { ++ super(); ++ } ++ ++ @Override ++ protected int g() { ++ return 40; ++ } ++ ++ @Override ++ protected int h() { ++ return 100; ++ } ++ ++ @Override ++ protected void j() { ++ EntityLiving entityliving = EntityEvoker.this.getGoalTarget(); ++ double d0 = Math.min(entityliving.locY(), EntityEvoker.this.locY()); ++ double d1 = Math.max(entityliving.locY(), EntityEvoker.this.locY()) + 1.0D; ++ float f = (float) MathHelper.d(entityliving.locZ() - EntityEvoker.this.locZ(), entityliving.locX() - EntityEvoker.this.locX()); ++ int i; ++ ++ if (EntityEvoker.this.h((Entity) entityliving) < 9.0D) { ++ float f1; ++ ++ for (i = 0; i < 5; ++i) { ++ f1 = f + (float) i * 3.1415927F * 0.4F; ++ this.a(EntityEvoker.this.locX() + (double) MathHelper.cos(f1) * 1.5D, EntityEvoker.this.locZ() + (double) MathHelper.sin(f1) * 1.5D, d0, d1, f1, 0); ++ } ++ ++ for (i = 0; i < 8; ++i) { ++ f1 = f + (float) i * 3.1415927F * 2.0F / 8.0F + 1.2566371F; ++ this.a(EntityEvoker.this.locX() + (double) MathHelper.cos(f1) * 2.5D, EntityEvoker.this.locZ() + (double) MathHelper.sin(f1) * 2.5D, d0, d1, f1, 3); ++ } ++ } else { ++ for (i = 0; i < 16; ++i) { ++ double d2 = 1.25D * (double) (i + 1); ++ int j = 1 * i; ++ ++ this.a(EntityEvoker.this.locX() + (double) MathHelper.cos(f) * d2, EntityEvoker.this.locZ() + (double) MathHelper.sin(f) * d2, d0, d1, f, j); ++ } ++ } ++ ++ } ++ ++ private void a(double d0, double d1, double d2, double d3, float f, int i) { ++ BlockPosition blockposition = new BlockPosition(d0, d3, d1); ++ boolean flag = false; ++ double d4 = 0.0D; ++ ++ do { ++ BlockPosition blockposition1 = blockposition.down(); ++ IBlockData iblockdata = EntityEvoker.this.world.getType(blockposition1); ++ ++ if (iblockdata.d(EntityEvoker.this.world, blockposition1, EnumDirection.UP)) { ++ if (!EntityEvoker.this.world.isEmpty(blockposition)) { ++ IBlockData iblockdata1 = EntityEvoker.this.world.getType(blockposition); ++ VoxelShape voxelshape = iblockdata1.getCollisionShape(EntityEvoker.this.world, blockposition); ++ ++ if (!voxelshape.isEmpty()) { ++ d4 = voxelshape.c(EnumDirection.EnumAxis.Y); ++ } ++ } ++ ++ flag = true; ++ break; ++ } ++ ++ blockposition = blockposition.down(); ++ } while (blockposition.getY() >= MathHelper.floor(d2) - 1); ++ ++ if (flag) { ++ EntityEvoker.this.world.addEntity(new EntityEvokerFangs(EntityEvoker.this.world, d0, (double) blockposition.getY() + d4, d1, f, i, EntityEvoker.this)); ++ } ++ ++ } ++ ++ @Override ++ protected SoundEffect k() { ++ return SoundEffects.ENTITY_EVOKER_PREPARE_ATTACK; ++ } ++ ++ @Override ++ protected EntityIllagerWizard.Spell getCastSpell() { ++ return EntityIllagerWizard.Spell.FANGS; ++ } ++ } ++ ++ class b extends EntityIllagerWizard.b { ++ ++ private b() { ++ super(); ++ } ++ ++ @Override ++ public void e() { ++ if (EntityEvoker.this.getGoalTarget() != null) { ++ EntityEvoker.this.getControllerLook().a(EntityEvoker.this.getGoalTarget(), (float) EntityEvoker.this.Q(), (float) EntityEvoker.this.O()); ++ } else if (EntityEvoker.this.fg() != null) { ++ EntityEvoker.this.getControllerLook().a(EntityEvoker.this.fg(), (float) EntityEvoker.this.Q(), (float) EntityEvoker.this.O()); ++ } ++ ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityMonster.java b/src/main/java/net/minecraft/world/entity/monster/EntityMonster.java +new file mode 100644 +index 0000000000000000000000000000000000000000..acebee991eca1e19fc1094718dc40822b66756e1 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityMonster.java +@@ -0,0 +1,143 @@ ++package net.minecraft.world.entity.monster; ++ ++import java.util.Random; ++import java.util.function.Predicate; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.sounds.SoundEffect; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.world.EnumDifficulty; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.EntityCreature; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.EnumMobSpawn; ++import net.minecraft.world.entity.ai.attributes.AttributeProvider; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.item.ItemProjectileWeapon; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.GeneratorAccess; ++import net.minecraft.world.level.IWorldReader; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.WorldAccess; ++ ++public abstract class EntityMonster extends EntityCreature implements IMonster { ++ ++ protected EntityMonster(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ this.f = 5; ++ } ++ ++ @Override ++ public SoundCategory getSoundCategory() { ++ return SoundCategory.HOSTILE; ++ } ++ ++ @Override ++ public void movementTick() { ++ this.dA(); ++ this.eQ(); ++ super.movementTick(); ++ } ++ ++ protected void eQ() { ++ float f = this.aR(); ++ ++ if (f > 0.5F) { ++ this.ticksFarFromPlayer += 2; ++ } ++ ++ } ++ ++ @Override ++ protected boolean L() { ++ return true; ++ } ++ ++ @Override ++ protected SoundEffect getSoundSwim() { ++ return SoundEffects.ENTITY_HOSTILE_SWIM; ++ } ++ ++ @Override ++ protected SoundEffect getSoundSplash() { ++ return SoundEffects.ENTITY_HOSTILE_SPLASH; ++ } ++ ++ @Override ++ public boolean damageEntity(DamageSource damagesource, float f) { ++ return this.isInvulnerable(damagesource) ? false : super.damageEntity(damagesource, f); ++ } ++ ++ @Override ++ protected SoundEffect getSoundHurt(DamageSource damagesource) { ++ return SoundEffects.ENTITY_HOSTILE_HURT; ++ } ++ ++ @Override ++ protected SoundEffect getSoundDeath() { ++ return SoundEffects.ENTITY_HOSTILE_DEATH; ++ } ++ ++ @Override ++ protected SoundEffect getSoundFall(int i) { ++ return i > 4 ? SoundEffects.ENTITY_HOSTILE_BIG_FALL : SoundEffects.ENTITY_HOSTILE_SMALL_FALL; ++ } ++ ++ @Override ++ public float a(BlockPosition blockposition, IWorldReader iworldreader) { ++ return 0.5F - iworldreader.y(blockposition); ++ } ++ ++ public static boolean a(WorldAccess worldaccess, BlockPosition blockposition, Random random) { ++ if (worldaccess.getBrightness(EnumSkyBlock.SKY, blockposition) > random.nextInt(32)) { ++ return false; ++ } else { ++ int i = worldaccess.getMinecraftWorld().W() ? worldaccess.c(blockposition, 10) : worldaccess.getLightLevel(blockposition); ++ ++ return i <= random.nextInt(8); ++ } ++ } ++ ++ public static boolean b(EntityTypes entitytypes, WorldAccess worldaccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) { ++ return worldaccess.getDifficulty() != EnumDifficulty.PEACEFUL && a(worldaccess, blockposition, random) && a(entitytypes, (GeneratorAccess) worldaccess, enummobspawn, blockposition, random); ++ } ++ ++ public static boolean c(EntityTypes entitytypes, GeneratorAccess generatoraccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) { ++ return generatoraccess.getDifficulty() != EnumDifficulty.PEACEFUL && a(entitytypes, generatoraccess, enummobspawn, blockposition, random); ++ } ++ ++ public static AttributeProvider.Builder eR() { ++ return EntityInsentient.p().a(GenericAttributes.ATTACK_DAMAGE); ++ } ++ ++ @Override ++ protected boolean isDropExperience() { ++ return true; ++ } ++ ++ @Override ++ protected boolean cW() { ++ return true; ++ } ++ ++ public boolean f(EntityHuman entityhuman) { ++ return true; ++ } ++ ++ @Override ++ public ItemStack f(ItemStack itemstack) { ++ if (itemstack.getItem() instanceof ItemProjectileWeapon) { ++ Predicate predicate = ((ItemProjectileWeapon) itemstack.getItem()).e(); ++ ItemStack itemstack1 = ItemProjectileWeapon.a((EntityLiving) this, predicate); ++ ++ return itemstack1.isEmpty() ? new ItemStack(Items.ARROW) : itemstack1; ++ } else { ++ return ItemStack.b; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java b/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fc1f0cd4b70cdd0dda538d8867fab4cb8443120e +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java +@@ -0,0 +1,249 @@ ++package net.minecraft.world.entity.monster; ++ ++import com.google.common.collect.Maps; ++import java.util.EnumSet; ++import java.util.Map; ++import java.util.function.Predicate; ++import javax.annotation.Nullable; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.sounds.SoundEffect; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.world.DifficultyDamageScaler; ++import net.minecraft.world.EnumDifficulty; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.EnumItemSlot; ++import net.minecraft.world.entity.EnumMobSpawn; ++import net.minecraft.world.entity.EnumMonsterType; ++import net.minecraft.world.entity.GroupDataEntity; ++import net.minecraft.world.entity.ai.attributes.AttributeProvider; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; ++import net.minecraft.world.entity.ai.goal.PathfinderGoal; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalBreakDoor; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalFloat; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalLookAtPlayer; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalMeleeAttack; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalRandomStroll; ++import net.minecraft.world.entity.ai.goal.target.PathfinderGoalHurtByTarget; ++import net.minecraft.world.entity.ai.goal.target.PathfinderGoalNearestAttackableTarget; ++import net.minecraft.world.entity.ai.navigation.Navigation; ++import net.minecraft.world.entity.ai.util.PathfinderGoalUtil; ++import net.minecraft.world.entity.animal.EntityIronGolem; ++import net.minecraft.world.entity.npc.EntityVillagerAbstract; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.raid.EntityRaider; ++import net.minecraft.world.entity.raid.Raid; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.item.enchantment.Enchantment; ++import net.minecraft.world.item.enchantment.EnchantmentManager; ++import net.minecraft.world.item.enchantment.Enchantments; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.WorldAccess; ++ ++public class EntityVindicator extends EntityIllagerAbstract { ++ ++ private static final Predicate b = (enumdifficulty) -> { ++ return enumdifficulty == EnumDifficulty.NORMAL || enumdifficulty == EnumDifficulty.HARD; ++ }; ++ private boolean bo; ++ ++ public EntityVindicator(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ } ++ ++ @Override ++ protected void initPathfinder() { ++ super.initPathfinder(); ++ this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(1, new EntityVindicator.a(this)); ++ this.goalSelector.a(2, new EntityIllagerAbstract.b(this)); ++ this.goalSelector.a(3, new EntityRaider.a(this, 10.0F)); ++ this.goalSelector.a(4, new EntityVindicator.c(this)); ++ this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a()); ++ this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); ++ this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, true)); ++ this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, true)); ++ this.targetSelector.a(4, new EntityVindicator.b(this)); ++ this.goalSelector.a(8, new PathfinderGoalRandomStroll(this, 0.6D)); ++ this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 3.0F, 1.0F)); ++ this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F)); ++ } ++ ++ @Override ++ protected void mobTick() { ++ if (!this.isNoAI() && PathfinderGoalUtil.a(this)) { ++ boolean flag = ((WorldServer) this.world).c_(this.getChunkCoordinates()); ++ ++ ((Navigation) this.getNavigation()).a(flag); ++ } ++ ++ super.mobTick(); ++ } ++ ++ public static AttributeProvider.Builder eK() { ++ return EntityMonster.eR().a(GenericAttributes.MOVEMENT_SPEED, 0.3499999940395355D).a(GenericAttributes.FOLLOW_RANGE, 12.0D).a(GenericAttributes.MAX_HEALTH, 24.0D).a(GenericAttributes.ATTACK_DAMAGE, 5.0D); ++ } ++ ++ @Override ++ public void saveData(NBTTagCompound nbttagcompound) { ++ super.saveData(nbttagcompound); ++ if (this.bo) { ++ nbttagcompound.setBoolean("Johnny", true); ++ } ++ ++ } ++ ++ @Override ++ public void loadData(NBTTagCompound nbttagcompound) { ++ super.loadData(nbttagcompound); ++ if (nbttagcompound.hasKeyOfType("Johnny", 99)) { ++ this.bo = nbttagcompound.getBoolean("Johnny"); ++ } ++ ++ } ++ ++ @Override ++ public SoundEffect eL() { ++ return SoundEffects.ENTITY_VINDICATOR_CELEBRATE; ++ } ++ ++ @Nullable ++ @Override ++ public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @Nullable GroupDataEntity groupdataentity, @Nullable NBTTagCompound nbttagcompound) { ++ GroupDataEntity groupdataentity1 = super.prepare(worldaccess, difficultydamagescaler, enummobspawn, groupdataentity, nbttagcompound); ++ ++ ((Navigation) this.getNavigation()).a(true); ++ this.a(difficultydamagescaler); ++ this.b(difficultydamagescaler); ++ return groupdataentity1; ++ } ++ ++ @Override ++ protected void a(DifficultyDamageScaler difficultydamagescaler) { ++ if (this.fa() == null) { ++ this.setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.IRON_AXE)); ++ } ++ ++ } ++ ++ @Override ++ public boolean r(Entity entity) { ++ return super.r(entity) ? true : (entity instanceof EntityLiving && ((EntityLiving) entity).getMonsterType() == EnumMonsterType.ILLAGER ? this.getScoreboardTeam() == null && entity.getScoreboardTeam() == null : false); ++ } ++ ++ @Override ++ public void setCustomName(@Nullable IChatBaseComponent ichatbasecomponent) { ++ super.setCustomName(ichatbasecomponent); ++ if (!this.bo && ichatbasecomponent != null && ichatbasecomponent.getString().equals("Johnny")) { ++ this.bo = true; ++ } ++ ++ } ++ ++ @Override ++ protected SoundEffect getSoundAmbient() { ++ return SoundEffects.ENTITY_VINDICATOR_AMBIENT; ++ } ++ ++ @Override ++ protected SoundEffect getSoundDeath() { ++ return SoundEffects.ENTITY_VINDICATOR_DEATH; ++ } ++ ++ @Override ++ protected SoundEffect getSoundHurt(DamageSource damagesource) { ++ return SoundEffects.ENTITY_VINDICATOR_HURT; ++ } ++ ++ @Override ++ public void a(int i, boolean flag) { ++ ItemStack itemstack = new ItemStack(Items.IRON_AXE); ++ Raid raid = this.fa(); ++ byte b0 = 1; ++ ++ if (i > raid.a(EnumDifficulty.NORMAL)) { ++ b0 = 2; ++ } ++ ++ boolean flag1 = this.random.nextFloat() <= raid.w(); ++ ++ if (flag1) { ++ Map map = Maps.newHashMap(); ++ ++ map.put(Enchantments.DAMAGE_ALL, Integer.valueOf(b0)); ++ EnchantmentManager.a((Map) map, itemstack); ++ } ++ ++ this.setSlot(EnumItemSlot.MAINHAND, itemstack); ++ } ++ ++ static class b extends PathfinderGoalNearestAttackableTarget { ++ ++ public b(EntityVindicator entityvindicator) { ++ super(entityvindicator, EntityLiving.class, 0, true, true, EntityLiving::ei); ++ } ++ ++ @Override ++ public boolean a() { ++ return ((EntityVindicator) this.e).bo && super.a(); ++ } ++ ++ @Override ++ public void c() { ++ super.c(); ++ this.e.n(0); ++ } ++ } ++ ++ static class a extends PathfinderGoalBreakDoor { ++ ++ public a(EntityInsentient entityinsentient) { ++ super(entityinsentient, 6, EntityVindicator.b); ++ this.a(EnumSet.of(PathfinderGoal.Type.MOVE)); ++ } ++ ++ @Override ++ public boolean b() { ++ EntityVindicator entityvindicator = (EntityVindicator) this.entity; ++ ++ return entityvindicator.fb() && super.b(); ++ } ++ ++ @Override ++ public boolean a() { ++ EntityVindicator entityvindicator = (EntityVindicator) this.entity; ++ ++ return entityvindicator.fb() && entityvindicator.random.nextInt(10) == 0 && super.a(); ++ } ++ ++ @Override ++ public void c() { ++ super.c(); ++ this.entity.n(0); ++ } ++ } ++ ++ class c extends PathfinderGoalMeleeAttack { ++ ++ public c(EntityVindicator entityvindicator) { ++ super(entityvindicator, 1.0D, false); ++ } ++ ++ @Override ++ protected double a(EntityLiving entityliving) { ++ if (this.a.getVehicle() instanceof EntityRavager) { ++ float f = this.a.getVehicle().getWidth() - 0.1F; ++ ++ return (double) (f * 2.0F * f * 2.0F + entityliving.getWidth()); ++ } else { ++ return super.a(entityliving); ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/monster/IRangedEntity.java b/src/main/java/net/minecraft/world/entity/monster/IRangedEntity.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d79e6b28c77edc468c6471d909306c2135b496c7 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/monster/IRangedEntity.java +@@ -0,0 +1,8 @@ ++package net.minecraft.world.entity.monster; ++ ++import net.minecraft.world.entity.EntityLiving; ++ ++public interface IRangedEntity { ++ ++ void a(EntityLiving entityliving, float f); ++} +diff --git a/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fe1dde99f758daa730acacc78237d92aa443ab6d +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java +@@ -0,0 +1,408 @@ ++package net.minecraft.world.entity.npc; ++ ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import it.unimi.dsi.fastutil.ints.Int2ObjectMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Random; ++import java.util.stream.Collectors; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.IRegistry; ++import net.minecraft.network.chat.ChatMessage; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.effect.MobEffectList; ++import net.minecraft.world.effect.MobEffects; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.item.EnumColor; ++import net.minecraft.world.item.IDyeable; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.item.ItemArmorColorable; ++import net.minecraft.world.item.ItemDye; ++import net.minecraft.world.item.ItemEnchantedBook; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.ItemSuspiciousStew; ++import net.minecraft.world.item.ItemWorldMap; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.item.alchemy.PotionBrewer; ++import net.minecraft.world.item.alchemy.PotionRegistry; ++import net.minecraft.world.item.alchemy.PotionUtil; ++import net.minecraft.world.item.enchantment.Enchantment; ++import net.minecraft.world.item.enchantment.EnchantmentManager; ++import net.minecraft.world.item.enchantment.WeightedRandomEnchant; ++import net.minecraft.world.item.trading.MerchantRecipe; ++import net.minecraft.world.level.IMaterial; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.levelgen.feature.StructureGenerator; ++import net.minecraft.world.level.saveddata.maps.MapIcon; ++import net.minecraft.world.level.saveddata.maps.WorldMap; ++ ++public class VillagerTrades { ++ ++ public static final Map> a = (Map) SystemUtils.a((Object) Maps.newHashMap(), (hashmap) -> { ++ hashmap.put(VillagerProfession.FARMER, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.WHEAT, 20, 16, 2), new VillagerTrades.b(Items.POTATO, 26, 16, 2), new VillagerTrades.b(Items.CARROT, 22, 16, 2), new VillagerTrades.b(Items.BEETROOT, 15, 16, 2), new VillagerTrades.h(Items.BREAD, 1, 6, 16, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Blocks.PUMPKIN, 6, 12, 10), new VillagerTrades.h(Items.PUMPKIN_PIE, 1, 4, 5), new VillagerTrades.h(Items.APPLE, 1, 4, 16, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.COOKIE, 3, 18, 10), new VillagerTrades.b(Blocks.MELON, 4, 12, 20)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Blocks.CAKE, 1, 1, 12, 15), new VillagerTrades.i(MobEffects.NIGHT_VISION, 100, 15), new VillagerTrades.i(MobEffects.JUMP, 160, 15), new VillagerTrades.i(MobEffects.WEAKNESS, 140, 15), new VillagerTrades.i(MobEffects.BLINDNESS, 120, 15), new VillagerTrades.i(MobEffects.POISON, 280, 15), new VillagerTrades.i(MobEffects.SATURATION, 7, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.GOLDEN_CARROT, 3, 3, 30), new VillagerTrades.h(Items.GLISTERING_MELON_SLICE, 4, 3, 30)}))); ++ hashmap.put(VillagerProfession.FISHERMAN, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.STRING, 20, 16, 2), new VillagerTrades.b(Items.COAL, 10, 16, 2), new VillagerTrades.g(Items.COD, 6, Items.COOKED_COD, 6, 16, 1), new VillagerTrades.h(Items.COD_BUCKET, 3, 1, 16, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.COD, 15, 16, 10), new VillagerTrades.g(Items.SALMON, 6, Items.COOKED_SALMON, 6, 16, 5), new VillagerTrades.h(Items.rn, 2, 1, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.SALMON, 13, 16, 20), new VillagerTrades.e(Items.FISHING_ROD, 3, 3, 10, 0.2F)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.TROPICAL_FISH, 6, 12, 30)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.PUFFERFISH, 4, 12, 30), new VillagerTrades.c(1, 12, 30, ImmutableMap.builder().put(VillagerType.PLAINS, Items.OAK_BOAT).put(VillagerType.TAIGA, Items.SPRUCE_BOAT).put(VillagerType.SNOW, Items.SPRUCE_BOAT).put(VillagerType.DESERT, Items.JUNGLE_BOAT).put(VillagerType.JUNGLE, Items.JUNGLE_BOAT).put(VillagerType.SAVANNA, Items.ACACIA_BOAT).put(VillagerType.SWAMP, Items.DARK_OAK_BOAT).build())}))); ++ hashmap.put(VillagerProfession.SHEPHERD, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Blocks.WHITE_WOOL, 18, 16, 2), new VillagerTrades.b(Blocks.BROWN_WOOL, 18, 16, 2), new VillagerTrades.b(Blocks.BLACK_WOOL, 18, 16, 2), new VillagerTrades.b(Blocks.GRAY_WOOL, 18, 16, 2), new VillagerTrades.h(Items.SHEARS, 2, 1, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.WHITE_DYE, 12, 16, 10), new VillagerTrades.b(Items.GRAY_DYE, 12, 16, 10), new VillagerTrades.b(Items.BLACK_DYE, 12, 16, 10), new VillagerTrades.b(Items.LIGHT_BLUE_DYE, 12, 16, 10), new VillagerTrades.b(Items.LIME_DYE, 12, 16, 10), new VillagerTrades.h(Blocks.WHITE_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.ORANGE_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.MAGENTA_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.LIGHT_BLUE_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.YELLOW_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.LIME_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.PINK_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.GRAY_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.LIGHT_GRAY_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.CYAN_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.PURPLE_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.BLUE_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.BROWN_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.GREEN_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.RED_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.BLACK_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.WHITE_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.ORANGE_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.MAGENTA_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.LIGHT_BLUE_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.YELLOW_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.LIME_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.PINK_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.GRAY_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.LIGHT_GRAY_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.CYAN_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.PURPLE_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.BLUE_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.BROWN_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.GREEN_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.RED_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.BLACK_CARPET, 1, 4, 16, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.YELLOW_DYE, 12, 16, 20), new VillagerTrades.b(Items.LIGHT_GRAY_DYE, 12, 16, 20), new VillagerTrades.b(Items.ORANGE_DYE, 12, 16, 20), new VillagerTrades.b(Items.RED_DYE, 12, 16, 20), new VillagerTrades.b(Items.PINK_DYE, 12, 16, 20), new VillagerTrades.h(Blocks.WHITE_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.YELLOW_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.RED_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.BLACK_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.BLUE_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.BROWN_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.CYAN_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.GRAY_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.GREEN_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.LIGHT_BLUE_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.LIGHT_GRAY_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.LIME_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.MAGENTA_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.ORANGE_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.PINK_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.PURPLE_BED, 3, 1, 12, 10)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.BROWN_DYE, 12, 16, 30), new VillagerTrades.b(Items.PURPLE_DYE, 12, 16, 30), new VillagerTrades.b(Items.BLUE_DYE, 12, 16, 30), new VillagerTrades.b(Items.GREEN_DYE, 12, 16, 30), new VillagerTrades.b(Items.MAGENTA_DYE, 12, 16, 30), new VillagerTrades.b(Items.CYAN_DYE, 12, 16, 30), new VillagerTrades.h(Items.WHITE_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.BLUE_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.LIGHT_BLUE_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.RED_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.PINK_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.GREEN_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.LIME_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.GRAY_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.BLACK_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.PURPLE_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.MAGENTA_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.CYAN_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.BROWN_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.YELLOW_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.ORANGE_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.LIGHT_GRAY_BANNER, 3, 1, 12, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.PAINTING, 2, 3, 30)}))); ++ hashmap.put(VillagerProfession.FLETCHER, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.STICK, 32, 16, 2), new VillagerTrades.h(Items.ARROW, 1, 16, 1), new VillagerTrades.g(Blocks.GRAVEL, 10, Items.FLINT, 10, 12, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.FLINT, 26, 12, 10), new VillagerTrades.h(Items.BOW, 2, 1, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.STRING, 14, 16, 20), new VillagerTrades.h(Items.CROSSBOW, 3, 1, 10)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.FEATHER, 24, 16, 30), new VillagerTrades.e(Items.BOW, 2, 3, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.es, 8, 12, 30), new VillagerTrades.e(Items.CROSSBOW, 3, 3, 15), new VillagerTrades.j(Items.ARROW, 5, Items.TIPPED_ARROW, 5, 2, 12, 30)}))); ++ hashmap.put(VillagerProfession.LIBRARIAN, a(ImmutableMap.builder().put(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.PAPER, 24, 16, 2), new VillagerTrades.d(1), new VillagerTrades.h(Blocks.BOOKSHELF, 9, 1, 12, 1)}).put(2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.BOOK, 4, 12, 10), new VillagerTrades.d(5), new VillagerTrades.h(Items.rk, 1, 1, 5)}).put(3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.INK_SAC, 5, 12, 20), new VillagerTrades.d(10), new VillagerTrades.h(Items.az, 1, 4, 10)}).put(4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.WRITABLE_BOOK, 2, 12, 30), new VillagerTrades.d(15), new VillagerTrades.h(Items.CLOCK, 5, 1, 15), new VillagerTrades.h(Items.COMPASS, 4, 1, 15)}).put(5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.NAME_TAG, 20, 1, 30)}).build())); ++ hashmap.put(VillagerProfession.CARTOGRAPHER, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.PAPER, 24, 16, 2), new VillagerTrades.h(Items.MAP, 7, 1, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.dP, 11, 16, 10), new VillagerTrades.k(13, StructureGenerator.MONUMENT, MapIcon.Type.MONUMENT, 12, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.COMPASS, 1, 12, 20), new VillagerTrades.k(14, StructureGenerator.MANSION, MapIcon.Type.MANSION, 12, 10)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.ITEM_FRAME, 7, 1, 15), new VillagerTrades.h(Items.WHITE_BANNER, 3, 1, 15), new VillagerTrades.h(Items.BLUE_BANNER, 3, 1, 15), new VillagerTrades.h(Items.LIGHT_BLUE_BANNER, 3, 1, 15), new VillagerTrades.h(Items.RED_BANNER, 3, 1, 15), new VillagerTrades.h(Items.PINK_BANNER, 3, 1, 15), new VillagerTrades.h(Items.GREEN_BANNER, 3, 1, 15), new VillagerTrades.h(Items.LIME_BANNER, 3, 1, 15), new VillagerTrades.h(Items.GRAY_BANNER, 3, 1, 15), new VillagerTrades.h(Items.BLACK_BANNER, 3, 1, 15), new VillagerTrades.h(Items.PURPLE_BANNER, 3, 1, 15), new VillagerTrades.h(Items.MAGENTA_BANNER, 3, 1, 15), new VillagerTrades.h(Items.CYAN_BANNER, 3, 1, 15), new VillagerTrades.h(Items.BROWN_BANNER, 3, 1, 15), new VillagerTrades.h(Items.YELLOW_BANNER, 3, 1, 15), new VillagerTrades.h(Items.ORANGE_BANNER, 3, 1, 15), new VillagerTrades.h(Items.LIGHT_GRAY_BANNER, 3, 1, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.GLOBE_BANNER_PATTERN, 8, 1, 30)}))); ++ hashmap.put(VillagerProfession.CLERIC, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.ROTTEN_FLESH, 32, 16, 2), new VillagerTrades.h(Items.REDSTONE, 1, 2, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.GOLD_INGOT, 3, 12, 10), new VillagerTrades.h(Items.LAPIS_LAZULI, 1, 1, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.RABBIT_FOOT, 2, 12, 20), new VillagerTrades.h(Blocks.GLOWSTONE, 4, 1, 12, 10)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.SCUTE, 4, 12, 30), new VillagerTrades.b(Items.GLASS_BOTTLE, 9, 12, 30), new VillagerTrades.h(Items.ENDER_PEARL, 5, 1, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.NETHER_WART, 22, 12, 30), new VillagerTrades.h(Items.EXPERIENCE_BOTTLE, 3, 1, 30)}))); ++ hashmap.put(VillagerProfession.ARMORER, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.COAL, 15, 16, 2), new VillagerTrades.h(new ItemStack(Items.IRON_LEGGINGS), 7, 1, 12, 1, 0.2F), new VillagerTrades.h(new ItemStack(Items.IRON_BOOTS), 4, 1, 12, 1, 0.2F), new VillagerTrades.h(new ItemStack(Items.IRON_HELMET), 5, 1, 12, 1, 0.2F), new VillagerTrades.h(new ItemStack(Items.IRON_CHESTPLATE), 9, 1, 12, 1, 0.2F)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.IRON_INGOT, 4, 12, 10), new VillagerTrades.h(new ItemStack(Items.rj), 36, 1, 12, 5, 0.2F), new VillagerTrades.h(new ItemStack(Items.CHAINMAIL_BOOTS), 1, 1, 12, 5, 0.2F), new VillagerTrades.h(new ItemStack(Items.CHAINMAIL_LEGGINGS), 3, 1, 12, 5, 0.2F)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.LAVA_BUCKET, 1, 12, 20), new VillagerTrades.b(Items.DIAMOND, 1, 12, 20), new VillagerTrades.h(new ItemStack(Items.CHAINMAIL_HELMET), 1, 1, 12, 10, 0.2F), new VillagerTrades.h(new ItemStack(Items.CHAINMAIL_CHESTPLATE), 4, 1, 12, 10, 0.2F), new VillagerTrades.h(new ItemStack(Items.SHIELD), 5, 1, 12, 10, 0.2F)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.e(Items.DIAMOND_LEGGINGS, 14, 3, 15, 0.2F), new VillagerTrades.e(Items.DIAMOND_BOOTS, 8, 3, 15, 0.2F)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.e(Items.DIAMOND_HELMET, 8, 3, 30, 0.2F), new VillagerTrades.e(Items.DIAMOND_CHESTPLATE, 16, 3, 30, 0.2F)}))); ++ hashmap.put(VillagerProfession.WEAPONSMITH, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.COAL, 15, 16, 2), new VillagerTrades.h(new ItemStack(Items.IRON_AXE), 3, 1, 12, 1, 0.2F), new VillagerTrades.e(Items.IRON_SWORD, 2, 3, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.IRON_INGOT, 4, 12, 10), new VillagerTrades.h(new ItemStack(Items.rj), 36, 1, 12, 5, 0.2F)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.FLINT, 24, 12, 20)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.DIAMOND, 1, 12, 30), new VillagerTrades.e(Items.DIAMOND_AXE, 12, 3, 15, 0.2F)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.e(Items.DIAMOND_SWORD, 8, 3, 30, 0.2F)}))); ++ hashmap.put(VillagerProfession.TOOLSMITH, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.COAL, 15, 16, 2), new VillagerTrades.h(new ItemStack(Items.STONE_AXE), 1, 1, 12, 1, 0.2F), new VillagerTrades.h(new ItemStack(Items.STONE_SHOVEL), 1, 1, 12, 1, 0.2F), new VillagerTrades.h(new ItemStack(Items.STONE_PICKAXE), 1, 1, 12, 1, 0.2F), new VillagerTrades.h(new ItemStack(Items.STONE_HOE), 1, 1, 12, 1, 0.2F)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.IRON_INGOT, 4, 12, 10), new VillagerTrades.h(new ItemStack(Items.rj), 36, 1, 12, 5, 0.2F)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.FLINT, 30, 12, 20), new VillagerTrades.e(Items.IRON_AXE, 1, 3, 10, 0.2F), new VillagerTrades.e(Items.IRON_SHOVEL, 2, 3, 10, 0.2F), new VillagerTrades.e(Items.IRON_PICKAXE, 3, 3, 10, 0.2F), new VillagerTrades.h(new ItemStack(Items.DIAMOND_HOE), 4, 1, 3, 10, 0.2F)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.DIAMOND, 1, 12, 30), new VillagerTrades.e(Items.DIAMOND_AXE, 12, 3, 15, 0.2F), new VillagerTrades.e(Items.DIAMOND_SHOVEL, 5, 3, 15, 0.2F)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.e(Items.DIAMOND_PICKAXE, 13, 3, 30, 0.2F)}))); ++ hashmap.put(VillagerProfession.BUTCHER, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.CHICKEN, 14, 16, 2), new VillagerTrades.b(Items.PORKCHOP, 7, 16, 2), new VillagerTrades.b(Items.RABBIT, 4, 16, 2), new VillagerTrades.h(Items.RABBIT_STEW, 1, 1, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.COAL, 15, 16, 2), new VillagerTrades.h(Items.COOKED_PORKCHOP, 1, 5, 16, 5), new VillagerTrades.h(Items.COOKED_CHICKEN, 1, 8, 16, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.MUTTON, 7, 16, 20), new VillagerTrades.b(Items.BEEF, 10, 16, 20)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.ma, 10, 12, 30)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.SWEET_BERRIES, 10, 12, 30)}))); ++ hashmap.put(VillagerProfession.LEATHERWORKER, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.LEATHER, 6, 16, 2), new VillagerTrades.a(Items.LEATHER_LEGGINGS, 3), new VillagerTrades.a(Items.LEATHER_CHESTPLATE, 7)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.FLINT, 26, 12, 10), new VillagerTrades.a(Items.LEATHER_HELMET, 5, 12, 5), new VillagerTrades.a(Items.LEATHER_BOOTS, 4, 12, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.RABBIT_HIDE, 9, 12, 20), new VillagerTrades.a(Items.LEATHER_CHESTPLATE, 7)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.SCUTE, 4, 12, 30), new VillagerTrades.a(Items.LEATHER_HORSE_ARMOR, 6, 12, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(new ItemStack(Items.SADDLE), 6, 1, 12, 30, 0.2F), new VillagerTrades.a(Items.LEATHER_HELMET, 5, 12, 30)}))); ++ hashmap.put(VillagerProfession.MASON, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.CLAY_BALL, 10, 16, 2), new VillagerTrades.h(Items.BRICK, 1, 10, 16, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Blocks.STONE, 20, 16, 10), new VillagerTrades.h(Blocks.CHISELED_STONE_BRICKS, 1, 4, 16, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Blocks.GRANITE, 16, 16, 20), new VillagerTrades.b(Blocks.ANDESITE, 16, 16, 20), new VillagerTrades.b(Blocks.DIORITE, 16, 16, 20), new VillagerTrades.h(Blocks.POLISHED_ANDESITE, 1, 4, 16, 10), new VillagerTrades.h(Blocks.POLISHED_DIORITE, 1, 4, 16, 10), new VillagerTrades.h(Blocks.POLISHED_GRANITE, 1, 4, 16, 10)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.QUARTZ, 12, 12, 30), new VillagerTrades.h(Blocks.ORANGE_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.WHITE_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.BLUE_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.LIGHT_BLUE_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.GRAY_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.LIGHT_GRAY_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.BLACK_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.RED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.PINK_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.MAGENTA_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.LIME_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.GREEN_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.CYAN_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.PURPLE_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.YELLOW_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.BROWN_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.ORANGE_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.WHITE_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.BLUE_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.LIGHT_BLUE_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.GRAY_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.LIGHT_GRAY_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.BLACK_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.RED_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.PINK_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.MAGENTA_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.LIME_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.GREEN_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.CYAN_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.PURPLE_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.YELLOW_GLAZED_TERRACOTTA, 1, 1, 12, 15), new VillagerTrades.h(Blocks.BROWN_GLAZED_TERRACOTTA, 1, 1, 12, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Blocks.QUARTZ_PILLAR, 1, 1, 12, 30), new VillagerTrades.h(Blocks.QUARTZ_BLOCK, 1, 1, 12, 30)}))); ++ }); ++ public static final Int2ObjectMap b = a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.aP, 2, 1, 5, 1), new VillagerTrades.h(Items.SLIME_BALL, 4, 1, 5, 1), new VillagerTrades.h(Items.dq, 2, 1, 5, 1), new VillagerTrades.h(Items.NAUTILUS_SHELL, 5, 1, 5, 1), new VillagerTrades.h(Items.aM, 1, 1, 12, 1), new VillagerTrades.h(Items.bD, 1, 1, 8, 1), new VillagerTrades.h(Items.di, 1, 1, 4, 1), new VillagerTrades.h(Items.bE, 3, 1, 12, 1), new VillagerTrades.h(Items.cX, 3, 1, 8, 1), new VillagerTrades.h(Items.bh, 1, 1, 12, 1), new VillagerTrades.h(Items.bi, 1, 1, 12, 1), new VillagerTrades.h(Items.bj, 1, 1, 8, 1), new VillagerTrades.h(Items.bk, 1, 1, 12, 1), new VillagerTrades.h(Items.bl, 1, 1, 12, 1), new VillagerTrades.h(Items.bm, 1, 1, 12, 1), new VillagerTrades.h(Items.bn, 1, 1, 12, 1), new VillagerTrades.h(Items.bo, 1, 1, 12, 1), new VillagerTrades.h(Items.bp, 1, 1, 12, 1), new VillagerTrades.h(Items.bq, 1, 1, 12, 1), new VillagerTrades.h(Items.br, 1, 1, 12, 1), new VillagerTrades.h(Items.bs, 1, 1, 7, 1), new VillagerTrades.h(Items.WHEAT_SEEDS, 1, 1, 12, 1), new VillagerTrades.h(Items.BEETROOT_SEEDS, 1, 1, 12, 1), new VillagerTrades.h(Items.PUMPKIN_SEEDS, 1, 1, 12, 1), new VillagerTrades.h(Items.MELON_SEEDS, 1, 1, 12, 1), new VillagerTrades.h(Items.B, 5, 1, 8, 1), new VillagerTrades.h(Items.z, 5, 1, 8, 1), new VillagerTrades.h(Items.C, 5, 1, 8, 1), new VillagerTrades.h(Items.A, 5, 1, 8, 1), new VillagerTrades.h(Items.x, 5, 1, 8, 1), new VillagerTrades.h(Items.y, 5, 1, 8, 1), new VillagerTrades.h(Items.RED_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.WHITE_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.BLUE_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.PINK_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.BLACK_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.GREEN_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.LIGHT_GRAY_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.MAGENTA_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.YELLOW_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.GRAY_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.PURPLE_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.LIGHT_BLUE_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.LIME_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.ORANGE_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.BROWN_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.CYAN_DYE, 1, 3, 12, 1), new VillagerTrades.h(Items.iJ, 3, 1, 8, 1), new VillagerTrades.h(Items.iK, 3, 1, 8, 1), new VillagerTrades.h(Items.iL, 3, 1, 8, 1), new VillagerTrades.h(Items.iM, 3, 1, 8, 1), new VillagerTrades.h(Items.iI, 3, 1, 8, 1), new VillagerTrades.h(Items.dR, 1, 1, 12, 1), new VillagerTrades.h(Items.bu, 1, 1, 12, 1), new VillagerTrades.h(Items.bv, 1, 1, 12, 1), new VillagerTrades.h(Items.ed, 1, 2, 5, 1), new VillagerTrades.h(Items.E, 1, 8, 8, 1), new VillagerTrades.h(Items.F, 1, 4, 6, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.TROPICAL_FISH_BUCKET, 5, 1, 4, 1), new VillagerTrades.h(Items.PUFFERFISH_BUCKET, 5, 1, 4, 1), new VillagerTrades.h(Items.ge, 3, 1, 6, 1), new VillagerTrades.h(Items.jh, 6, 1, 6, 1), new VillagerTrades.h(Items.GUNPOWDER, 1, 1, 8, 1), new VillagerTrades.h(Items.l, 3, 3, 6, 1)})); ++ ++ private static Int2ObjectMap a(ImmutableMap immutablemap) { ++ return new Int2ObjectOpenHashMap(immutablemap); ++ } ++ ++ static class g implements VillagerTrades.IMerchantRecipeOption { ++ ++ private final ItemStack a; ++ private final int b; ++ private final int c; ++ private final ItemStack d; ++ private final int e; ++ private final int f; ++ private final int g; ++ private final float h; ++ ++ public g(IMaterial imaterial, int i, Item item, int j, int k, int l) { ++ this(imaterial, i, 1, item, j, k, l); ++ } ++ ++ public g(IMaterial imaterial, int i, int j, Item item, int k, int l, int i1) { ++ this.a = new ItemStack(imaterial); ++ this.b = i; ++ this.c = j; ++ this.d = new ItemStack(item); ++ this.e = k; ++ this.f = l; ++ this.g = i1; ++ this.h = 0.05F; ++ } ++ ++ @Nullable ++ @Override ++ public MerchantRecipe a(Entity entity, Random random) { ++ return new MerchantRecipe(new ItemStack(Items.EMERALD, this.c), new ItemStack(this.a.getItem(), this.b), new ItemStack(this.d.getItem(), this.e), this.f, this.g, this.h); ++ } ++ } ++ ++ static class k implements VillagerTrades.IMerchantRecipeOption { ++ ++ private final int a; ++ private final StructureGenerator b; ++ private final MapIcon.Type c; ++ private final int d; ++ private final int e; ++ ++ public k(int i, StructureGenerator structuregenerator, MapIcon.Type mapicon_type, int j, int k) { ++ this.a = i; ++ this.b = structuregenerator; ++ this.c = mapicon_type; ++ this.d = j; ++ this.e = k; ++ } ++ ++ @Nullable ++ @Override ++ public MerchantRecipe a(Entity entity, Random random) { ++ if (!(entity.world instanceof WorldServer)) { ++ return null; ++ } else { ++ WorldServer worldserver = (WorldServer) entity.world; ++ BlockPosition blockposition = worldserver.a(this.b, entity.getChunkCoordinates(), 100, true); ++ ++ if (blockposition != null) { ++ ItemStack itemstack = ItemWorldMap.createFilledMapView(worldserver, blockposition.getX(), blockposition.getZ(), (byte) 2, true, true); ++ ++ ItemWorldMap.applySepiaFilter(worldserver, itemstack); ++ WorldMap.decorateMap(itemstack, blockposition, "+", this.c); ++ itemstack.a((IChatBaseComponent) (new ChatMessage("filled_map." + this.b.i().toLowerCase(Locale.ROOT)))); ++ return new MerchantRecipe(new ItemStack(Items.EMERALD, this.a), new ItemStack(Items.COMPASS), itemstack, this.d, this.e, 0.2F); ++ } else { ++ return null; ++ } ++ } ++ } ++ } ++ ++ static class d implements VillagerTrades.IMerchantRecipeOption { ++ ++ private final int a; ++ ++ public d(int i) { ++ this.a = i; ++ } ++ ++ @Override ++ public MerchantRecipe a(Entity entity, Random random) { ++ List list = (List) IRegistry.ENCHANTMENT.g().filter(Enchantment::h).collect(Collectors.toList()); ++ Enchantment enchantment = (Enchantment) list.get(random.nextInt(list.size())); ++ int i = MathHelper.nextInt(random, enchantment.getStartLevel(), enchantment.getMaxLevel()); ++ ItemStack itemstack = ItemEnchantedBook.a(new WeightedRandomEnchant(enchantment, i)); ++ int j = 2 + random.nextInt(5 + i * 10) + 3 * i; ++ ++ if (enchantment.isTreasure()) { ++ j *= 2; ++ } ++ ++ if (j > 64) { ++ j = 64; ++ } ++ ++ return new MerchantRecipe(new ItemStack(Items.EMERALD, j), new ItemStack(Items.BOOK), itemstack, 12, this.a, 0.2F); ++ } ++ } ++ ++ static class a implements VillagerTrades.IMerchantRecipeOption { ++ ++ private final Item a; ++ private final int b; ++ private final int c; ++ private final int d; ++ ++ public a(Item item, int i) { ++ this(item, i, 12, 1); ++ } ++ ++ public a(Item item, int i, int j, int k) { ++ this.a = item; ++ this.b = i; ++ this.c = j; ++ this.d = k; ++ } ++ ++ @Override ++ public MerchantRecipe a(Entity entity, Random random) { ++ ItemStack itemstack = new ItemStack(Items.EMERALD, this.b); ++ ItemStack itemstack1 = new ItemStack(this.a); ++ ++ if (this.a instanceof ItemArmorColorable) { ++ List list = Lists.newArrayList(); ++ ++ list.add(a(random)); ++ if (random.nextFloat() > 0.7F) { ++ list.add(a(random)); ++ } ++ ++ if (random.nextFloat() > 0.8F) { ++ list.add(a(random)); ++ } ++ ++ itemstack1 = IDyeable.a(itemstack1, list); ++ } ++ ++ return new MerchantRecipe(itemstack, itemstack1, this.c, this.d, 0.2F); ++ } ++ ++ private static ItemDye a(Random random) { ++ return ItemDye.a(EnumColor.fromColorIndex(random.nextInt(16))); ++ } ++ } ++ ++ static class j implements VillagerTrades.IMerchantRecipeOption { ++ ++ private final ItemStack a; ++ private final int b; ++ private final int c; ++ private final int d; ++ private final int e; ++ private final Item f; ++ private final int g; ++ private final float h; ++ ++ public j(Item item, int i, Item item1, int j, int k, int l, int i1) { ++ this.a = new ItemStack(item1); ++ this.c = k; ++ this.d = l; ++ this.e = i1; ++ this.f = item; ++ this.g = i; ++ this.b = j; ++ this.h = 0.05F; ++ } ++ ++ @Override ++ public MerchantRecipe a(Entity entity, Random random) { ++ ItemStack itemstack = new ItemStack(Items.EMERALD, this.c); ++ List list = (List) IRegistry.POTION.g().filter((potionregistry) -> { ++ return !potionregistry.a().isEmpty() && PotionBrewer.a(potionregistry); ++ }).collect(Collectors.toList()); ++ PotionRegistry potionregistry = (PotionRegistry) list.get(random.nextInt(list.size())); ++ ItemStack itemstack1 = PotionUtil.a(new ItemStack(this.a.getItem(), this.b), potionregistry); ++ ++ return new MerchantRecipe(itemstack, new ItemStack(this.f, this.g), itemstack1, this.d, this.e, this.h); ++ } ++ } ++ ++ static class e implements VillagerTrades.IMerchantRecipeOption { ++ ++ private final ItemStack a; ++ private final int b; ++ private final int c; ++ private final int d; ++ private final float e; ++ ++ public e(Item item, int i, int j, int k) { ++ this(item, i, j, k, 0.05F); ++ } ++ ++ public e(Item item, int i, int j, int k, float f) { ++ this.a = new ItemStack(item); ++ this.b = i; ++ this.c = j; ++ this.d = k; ++ this.e = f; ++ } ++ ++ @Override ++ public MerchantRecipe a(Entity entity, Random random) { ++ int i = 5 + random.nextInt(15); ++ ItemStack itemstack = EnchantmentManager.a(random, new ItemStack(this.a.getItem()), i, false); ++ int j = Math.min(this.b + i, 64); ++ ItemStack itemstack1 = new ItemStack(Items.EMERALD, j); ++ ++ return new MerchantRecipe(itemstack1, itemstack, this.c, this.d, this.e); ++ } ++ } ++ ++ static class i implements VillagerTrades.IMerchantRecipeOption { ++ ++ final MobEffectList a; ++ final int b; ++ final int c; ++ private final float d; ++ ++ public i(MobEffectList mobeffectlist, int i, int j) { ++ this.a = mobeffectlist; ++ this.b = i; ++ this.c = j; ++ this.d = 0.05F; ++ } ++ ++ @Nullable ++ @Override ++ public MerchantRecipe a(Entity entity, Random random) { ++ ItemStack itemstack = new ItemStack(Items.SUSPICIOUS_STEW, 1); ++ ++ ItemSuspiciousStew.a(itemstack, this.a, this.b); ++ return new MerchantRecipe(new ItemStack(Items.EMERALD, 1), itemstack, 12, this.c, this.d); ++ } ++ } ++ ++ static class h implements VillagerTrades.IMerchantRecipeOption { ++ ++ private final ItemStack a; ++ private final int b; ++ private final int c; ++ private final int d; ++ private final int e; ++ private final float f; ++ ++ public h(Block block, int i, int j, int k, int l) { ++ this(new ItemStack(block), i, j, k, l); ++ } ++ ++ public h(Item item, int i, int j, int k) { ++ this(new ItemStack(item), i, j, 12, k); ++ } ++ ++ public h(Item item, int i, int j, int k, int l) { ++ this(new ItemStack(item), i, j, k, l); ++ } ++ ++ public h(ItemStack itemstack, int i, int j, int k, int l) { ++ this(itemstack, i, j, k, l, 0.05F); ++ } ++ ++ public h(ItemStack itemstack, int i, int j, int k, int l, float f) { ++ this.a = itemstack; ++ this.b = i; ++ this.c = j; ++ this.d = k; ++ this.e = l; ++ this.f = f; ++ } ++ ++ @Override ++ public MerchantRecipe a(Entity entity, Random random) { ++ return new MerchantRecipe(new ItemStack(Items.EMERALD, this.b), new ItemStack(this.a.getItem(), this.c), this.d, this.e, this.f); ++ } ++ } ++ ++ static class c implements VillagerTrades.IMerchantRecipeOption { ++ ++ private final Map a; ++ private final int b; ++ private final int c; ++ private final int d; ++ ++ public c(int i, int j, int k, Map map) { ++ IRegistry.VILLAGER_TYPE.g().filter((villagertype) -> { ++ return !map.containsKey(villagertype); ++ }).findAny().ifPresent((villagertype) -> { ++ throw new IllegalStateException("Missing trade for villager type: " + IRegistry.VILLAGER_TYPE.getKey(villagertype)); ++ }); ++ this.a = map; ++ this.b = i; ++ this.c = j; ++ this.d = k; ++ } ++ ++ @Nullable ++ @Override ++ public MerchantRecipe a(Entity entity, Random random) { ++ if (entity instanceof VillagerDataHolder) { ++ ItemStack itemstack = new ItemStack((IMaterial) this.a.get(((VillagerDataHolder) entity).getVillagerData().getType()), this.b); ++ ++ return new MerchantRecipe(itemstack, new ItemStack(Items.EMERALD), this.c, this.d, 0.05F); ++ } else { ++ return null; ++ } ++ } ++ } ++ ++ static class b implements VillagerTrades.IMerchantRecipeOption { ++ ++ private final Item a; ++ private final int b; ++ private final int c; ++ private final int d; ++ private final float e; ++ ++ public b(IMaterial imaterial, int i, int j, int k) { ++ this.a = imaterial.getItem(); ++ this.b = i; ++ this.c = j; ++ this.d = k; ++ this.e = 0.05F; ++ } ++ ++ @Override ++ public MerchantRecipe a(Entity entity, Random random) { ++ ItemStack itemstack = new ItemStack(this.a, this.b); ++ ++ return new MerchantRecipe(itemstack, new ItemStack(Items.EMERALD), this.c, this.d, this.e); ++ } ++ } ++ ++ public interface IMerchantRecipeOption { ++ ++ @Nullable ++ MerchantRecipe a(Entity entity, Random random); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityDragonFireball.java b/src/main/java/net/minecraft/world/entity/projectile/EntityDragonFireball.java +new file mode 100644 +index 0000000000000000000000000000000000000000..59b5484731a5f71005c3efa56cbe40012d9641b5 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityDragonFireball.java +@@ -0,0 +1,88 @@ ++package net.minecraft.world.entity.projectile; ++ ++import java.util.Iterator; ++import java.util.List; ++import net.minecraft.core.particles.ParticleParam; ++import net.minecraft.core.particles.Particles; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.effect.MobEffect; ++import net.minecraft.world.effect.MobEffects; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityAreaEffectCloud; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.level.World; ++import net.minecraft.world.phys.MovingObjectPosition; ++import net.minecraft.world.phys.MovingObjectPositionEntity; ++ ++public class EntityDragonFireball extends EntityFireball { ++ ++ public EntityDragonFireball(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ } ++ ++ public EntityDragonFireball(World world, EntityLiving entityliving, double d0, double d1, double d2) { ++ super(EntityTypes.DRAGON_FIREBALL, entityliving, d0, d1, d2, world); ++ } ++ ++ @Override ++ protected void a(MovingObjectPosition movingobjectposition) { ++ super.a(movingobjectposition); ++ Entity entity = this.getShooter(); ++ ++ if (movingobjectposition.getType() != MovingObjectPosition.EnumMovingObjectType.ENTITY || !((MovingObjectPositionEntity) movingobjectposition).getEntity().s(entity)) { ++ if (!this.world.isClientSide) { ++ List list = this.world.a(EntityLiving.class, this.getBoundingBox().grow(4.0D, 2.0D, 4.0D)); ++ EntityAreaEffectCloud entityareaeffectcloud = new EntityAreaEffectCloud(this.world, this.locX(), this.locY(), this.locZ()); ++ ++ if (entity instanceof EntityLiving) { ++ entityareaeffectcloud.setSource((EntityLiving) entity); ++ } ++ ++ entityareaeffectcloud.setParticle(Particles.DRAGON_BREATH); ++ entityareaeffectcloud.setRadius(3.0F); ++ entityareaeffectcloud.setDuration(600); ++ entityareaeffectcloud.setRadiusPerTick((7.0F - entityareaeffectcloud.getRadius()) / (float) entityareaeffectcloud.getDuration()); ++ entityareaeffectcloud.addEffect(new MobEffect(MobEffects.HARM, 1, 1)); ++ if (!list.isEmpty()) { ++ Iterator iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ EntityLiving entityliving = (EntityLiving) iterator.next(); ++ double d0 = this.h(entityliving); ++ ++ if (d0 < 16.0D) { ++ entityareaeffectcloud.setPosition(entityliving.locX(), entityliving.locY(), entityliving.locZ()); ++ break; ++ } ++ } ++ } ++ ++ this.world.triggerEffect(2006, this.getChunkCoordinates(), this.isSilent() ? -1 : 1); ++ this.world.addEntity(entityareaeffectcloud); ++ this.die(); ++ } ++ ++ } ++ } ++ ++ @Override ++ public boolean isInteractable() { ++ return false; ++ } ++ ++ @Override ++ public boolean damageEntity(DamageSource damagesource, float f) { ++ return false; ++ } ++ ++ @Override ++ protected ParticleParam h() { ++ return Particles.DRAGON_BREATH; ++ } ++ ++ @Override ++ protected boolean W_() { ++ return false; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/EntityProjectile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..829b4f28896bcb0eb6e48242bd00585eeaae62c2 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityProjectile.java +@@ -0,0 +1,102 @@ ++package net.minecraft.world.entity.projectile; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.particles.Particles; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.entity.TileEntityEndGateway; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.phys.MovingObjectPosition; ++import net.minecraft.world.phys.MovingObjectPositionBlock; ++import net.minecraft.world.phys.Vec3D; ++ ++public abstract class EntityProjectile extends IProjectile { ++ ++ protected EntityProjectile(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ } ++ ++ protected EntityProjectile(EntityTypes entitytypes, double d0, double d1, double d2, World world) { ++ this(entitytypes, world); ++ this.setPosition(d0, d1, d2); ++ } ++ ++ protected EntityProjectile(EntityTypes entitytypes, EntityLiving entityliving, World world) { ++ this(entitytypes, entityliving.locX(), entityliving.getHeadY() - 0.10000000149011612D, entityliving.locZ(), world); ++ this.setShooter(entityliving); ++ } ++ ++ @Override ++ public void tick() { ++ super.tick(); ++ MovingObjectPosition movingobjectposition = ProjectileHelper.a((Entity) this, this::a); ++ boolean flag = false; ++ ++ if (movingobjectposition.getType() == MovingObjectPosition.EnumMovingObjectType.BLOCK) { ++ BlockPosition blockposition = ((MovingObjectPositionBlock) movingobjectposition).getBlockPosition(); ++ IBlockData iblockdata = this.world.getType(blockposition); ++ ++ if (iblockdata.a(Blocks.NETHER_PORTAL)) { ++ this.d(blockposition); ++ flag = true; ++ } else if (iblockdata.a(Blocks.END_GATEWAY)) { ++ TileEntity tileentity = this.world.getTileEntity(blockposition); ++ ++ if (tileentity instanceof TileEntityEndGateway && TileEntityEndGateway.a((Entity) this)) { ++ ((TileEntityEndGateway) tileentity).b((Entity) this); ++ } ++ ++ flag = true; ++ } ++ } ++ ++ if (movingobjectposition.getType() != MovingObjectPosition.EnumMovingObjectType.MISS && !flag) { ++ this.a(movingobjectposition); ++ } ++ ++ this.checkBlockCollisions(); ++ Vec3D vec3d = this.getMot(); ++ double d0 = this.locX() + vec3d.x; ++ double d1 = this.locY() + vec3d.y; ++ double d2 = this.locZ() + vec3d.z; ++ ++ this.x(); ++ float f; ++ ++ if (this.isInWater()) { ++ for (int i = 0; i < 4; ++i) { ++ float f1 = 0.25F; ++ ++ this.world.addParticle(Particles.BUBBLE, d0 - vec3d.x * 0.25D, d1 - vec3d.y * 0.25D, d2 - vec3d.z * 0.25D, vec3d.x, vec3d.y, vec3d.z); ++ } ++ ++ f = 0.8F; ++ } else { ++ f = 0.99F; ++ } ++ ++ this.setMot(vec3d.a((double) f)); ++ if (!this.isNoGravity()) { ++ Vec3D vec3d1 = this.getMot(); ++ ++ this.setMot(vec3d1.x, vec3d1.y - (double) this.k(), vec3d1.z); ++ } ++ ++ this.setPosition(d0, d1, d2); ++ } ++ ++ protected float k() { ++ return 0.03F; ++ } ++ ++ @Override ++ public Packet P() { ++ return new PacketPlayOutSpawnEntity(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartTNT.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartTNT.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ee31704ffb88ab68702657554d386e8ebfa05d03 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartTNT.java +@@ -0,0 +1,172 @@ ++package net.minecraft.world.entity.vehicle; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.particles.Particles; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsBlock; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.projectile.EntityArrow; ++import net.minecraft.world.level.Explosion; ++import net.minecraft.world.level.GameRules; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.IMaterial; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.material.Fluid; ++ ++public class EntityMinecartTNT extends EntityMinecartAbstract { ++ ++ private int b = -1; ++ ++ public EntityMinecartTNT(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ } ++ ++ public EntityMinecartTNT(World world, double d0, double d1, double d2) { ++ super(EntityTypes.TNT_MINECART, world, d0, d1, d2); ++ } ++ ++ @Override ++ public EntityMinecartAbstract.EnumMinecartType getMinecartType() { ++ return EntityMinecartAbstract.EnumMinecartType.TNT; ++ } ++ ++ @Override ++ public IBlockData q() { ++ return Blocks.TNT.getBlockData(); ++ } ++ ++ @Override ++ public void tick() { ++ super.tick(); ++ if (this.b > 0) { ++ --this.b; ++ this.world.addParticle(Particles.SMOKE, this.locX(), this.locY() + 0.5D, this.locZ(), 0.0D, 0.0D, 0.0D); ++ } else if (this.b == 0) { ++ this.h(c(this.getMot())); ++ } ++ ++ if (this.positionChanged) { ++ double d0 = c(this.getMot()); ++ ++ if (d0 >= 0.009999999776482582D) { ++ this.h(d0); ++ } ++ } ++ ++ } ++ ++ @Override ++ public boolean damageEntity(DamageSource damagesource, float f) { ++ Entity entity = damagesource.j(); ++ ++ if (entity instanceof EntityArrow) { ++ EntityArrow entityarrow = (EntityArrow) entity; ++ ++ if (entityarrow.isBurning()) { ++ this.h(entityarrow.getMot().g()); ++ } ++ } ++ ++ return super.damageEntity(damagesource, f); ++ } ++ ++ @Override ++ public void a(DamageSource damagesource) { ++ double d0 = c(this.getMot()); ++ ++ if (!damagesource.isFire() && !damagesource.isExplosion() && d0 < 0.009999999776482582D) { ++ super.a(damagesource); ++ if (!damagesource.isExplosion() && this.world.getGameRules().getBoolean(GameRules.DO_ENTITY_DROPS)) { ++ this.a((IMaterial) Blocks.TNT); ++ } ++ ++ } else { ++ if (this.b < 0) { ++ this.u(); ++ this.b = this.random.nextInt(20) + this.random.nextInt(20); ++ } ++ ++ } ++ } ++ ++ protected void h(double d0) { ++ if (!this.world.isClientSide) { ++ double d1 = Math.sqrt(d0); ++ ++ if (d1 > 5.0D) { ++ d1 = 5.0D; ++ } ++ ++ this.world.explode(this, this.locX(), this.locY(), this.locZ(), (float) (4.0D + this.random.nextDouble() * 1.5D * d1), Explosion.Effect.BREAK); ++ this.die(); ++ } ++ ++ } ++ ++ @Override ++ public boolean b(float f, float f1) { ++ if (f >= 3.0F) { ++ float f2 = f / 10.0F; ++ ++ this.h((double) (f2 * f2)); ++ } ++ ++ return super.b(f, f1); ++ } ++ ++ @Override ++ public void a(int i, int j, int k, boolean flag) { ++ if (flag && this.b < 0) { ++ this.u(); ++ } ++ ++ } ++ ++ public void u() { ++ this.b = 80; ++ if (!this.world.isClientSide) { ++ this.world.broadcastEntityEffect(this, (byte) 10); ++ if (!this.isSilent()) { ++ this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_TNT_PRIMED, SoundCategory.BLOCKS, 1.0F, 1.0F); ++ } ++ } ++ ++ } ++ ++ public boolean x() { ++ return this.b > -1; ++ } ++ ++ @Override ++ public float a(Explosion explosion, IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata, Fluid fluid, float f) { ++ return this.x() && (iblockdata.a((Tag) TagsBlock.RAILS) || iblockaccess.getType(blockposition.up()).a((Tag) TagsBlock.RAILS)) ? 0.0F : super.a(explosion, iblockaccess, blockposition, iblockdata, fluid, f); ++ } ++ ++ @Override ++ public boolean a(Explosion explosion, IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata, float f) { ++ return this.x() && (iblockdata.a((Tag) TagsBlock.RAILS) || iblockaccess.getType(blockposition.up()).a((Tag) TagsBlock.RAILS)) ? false : super.a(explosion, iblockaccess, blockposition, iblockdata, f); ++ } ++ ++ @Override ++ protected void loadData(NBTTagCompound nbttagcompound) { ++ super.loadData(nbttagcompound); ++ if (nbttagcompound.hasKeyOfType("TNTFuse", 99)) { ++ this.b = nbttagcompound.getInt("TNTFuse"); ++ } ++ ++ } ++ ++ @Override ++ protected void saveData(NBTTagCompound nbttagcompound) { ++ super.saveData(nbttagcompound); ++ nbttagcompound.setInt("TNTFuse", this.b); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerProperty.java b/src/main/java/net/minecraft/world/inventory/ContainerProperty.java +new file mode 100644 +index 0000000000000000000000000000000000000000..67c3b7ddb0b0f10c82577cbea7506c9d80d41368 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/inventory/ContainerProperty.java +@@ -0,0 +1,64 @@ ++package net.minecraft.world.inventory; ++ ++public abstract class ContainerProperty { ++ ++ private int a; ++ ++ public ContainerProperty() {} ++ ++ public static ContainerProperty a(final IContainerProperties icontainerproperties, final int i) { ++ return new ContainerProperty() { ++ @Override ++ public int get() { ++ return icontainerproperties.getProperty(i); ++ } ++ ++ @Override ++ public void set(int j) { ++ icontainerproperties.setProperty(i, j); ++ } ++ }; ++ } ++ ++ public static ContainerProperty a(final int[] aint, final int i) { ++ return new ContainerProperty() { ++ @Override ++ public int get() { ++ return aint[i]; ++ } ++ ++ @Override ++ public void set(int j) { ++ aint[i] = j; ++ } ++ }; ++ } ++ ++ public static ContainerProperty a() { ++ return new ContainerProperty() { ++ private int a; ++ ++ @Override ++ public int get() { ++ return this.a; ++ } ++ ++ @Override ++ public void set(int i) { ++ this.a = i; ++ } ++ }; ++ } ++ ++ public abstract int get(); ++ ++ public abstract void set(int i); ++ ++ public boolean c() { ++ int i = this.get(); ++ boolean flag = i != this.a; ++ ++ this.a = i; ++ return flag; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/item/Item.java b/src/main/java/net/minecraft/world/item/Item.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ca513e7b0a416860aba89e41de6a7c5ff42baa69 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/item/Item.java +@@ -0,0 +1,372 @@ ++package net.minecraft.world.item; ++ ++import com.google.common.collect.ImmutableMultimap; ++import com.google.common.collect.Maps; ++import com.google.common.collect.Multimap; ++import java.util.Map; ++import java.util.Random; ++import java.util.UUID; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.IRegistry; ++import net.minecraft.core.NonNullList; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.network.chat.ChatMessage; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.sounds.SoundEffect; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.tags.Tag; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; ++import net.minecraft.world.InteractionResultWrapper; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EnumItemSlot; ++import net.minecraft.world.entity.ai.attributes.AttributeBase; ++import net.minecraft.world.entity.ai.attributes.AttributeModifier; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.food.FoodInfo; ++import net.minecraft.world.item.context.ItemActionContext; ++import net.minecraft.world.level.IMaterial; ++import net.minecraft.world.level.RayTrace; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.phys.MovingObjectPositionBlock; ++import net.minecraft.world.phys.Vec3D; ++ ++public class Item implements IMaterial { ++ ++ public static final Map e = Maps.newHashMap(); ++ protected static final UUID f = UUID.fromString("CB3F55D3-645C-4F38-A497-9C13A33DB5CF"); ++ protected static final UUID g = UUID.fromString("FA233E1C-4180-4865-B01B-BCCE9785ACA3"); ++ protected static final Random RANDOM = new Random(); ++ protected final CreativeModeTab i; ++ private final EnumItemRarity a; ++ private final int maxStackSize; ++ private final int durability; ++ private final boolean d; ++ private final Item craftingResult; ++ @Nullable ++ private String name; ++ @Nullable ++ private final FoodInfo foodInfo; ++ ++ public static int getId(Item item) { ++ return item == null ? 0 : IRegistry.ITEM.a((Object) item); ++ } ++ ++ public static Item getById(int i) { ++ return (Item) IRegistry.ITEM.fromId(i); ++ } ++ ++ @Deprecated ++ public static Item getItemOf(Block block) { ++ return (Item) Item.e.getOrDefault(block, Items.AIR); ++ } ++ ++ public Item(Item.Info item_info) { ++ this.i = item_info.d; ++ this.a = item_info.e; ++ this.craftingResult = item_info.c; ++ this.durability = item_info.b; ++ this.maxStackSize = item_info.a; ++ this.foodInfo = item_info.f; ++ this.d = item_info.g; ++ } ++ ++ public void a(World world, EntityLiving entityliving, ItemStack itemstack, int i) {} ++ ++ public boolean b(NBTTagCompound nbttagcompound) { ++ return false; ++ } ++ ++ public boolean a(IBlockData iblockdata, World world, BlockPosition blockposition, EntityHuman entityhuman) { ++ return true; ++ } ++ ++ @Override ++ public Item getItem() { ++ return this; ++ } ++ ++ public EnumInteractionResult a(ItemActionContext itemactioncontext) { ++ return EnumInteractionResult.PASS; ++ } ++ ++ public float getDestroySpeed(ItemStack itemstack, IBlockData iblockdata) { ++ return 1.0F; ++ } ++ ++ public InteractionResultWrapper a(World world, EntityHuman entityhuman, EnumHand enumhand) { ++ if (this.isFood()) { ++ ItemStack itemstack = entityhuman.b(enumhand); ++ ++ if (entityhuman.q(this.getFoodInfo().d())) { ++ entityhuman.c(enumhand); ++ return InteractionResultWrapper.consume(itemstack); ++ } else { ++ return InteractionResultWrapper.fail(itemstack); ++ } ++ } else { ++ return InteractionResultWrapper.pass(entityhuman.b(enumhand)); ++ } ++ } ++ ++ public ItemStack a(ItemStack itemstack, World world, EntityLiving entityliving) { ++ return this.isFood() ? entityliving.a(world, itemstack) : itemstack; ++ } ++ ++ public final int getMaxStackSize() { ++ return this.maxStackSize; ++ } ++ ++ public final int getMaxDurability() { ++ return this.durability; ++ } ++ ++ public boolean usesDurability() { ++ return this.durability > 0; ++ } ++ ++ public boolean a(ItemStack itemstack, EntityLiving entityliving, EntityLiving entityliving1) { ++ return false; ++ } ++ ++ public boolean a(ItemStack itemstack, World world, IBlockData iblockdata, BlockPosition blockposition, EntityLiving entityliving) { ++ return false; ++ } ++ ++ public boolean canDestroySpecialBlock(IBlockData iblockdata) { ++ return false; ++ } ++ ++ public EnumInteractionResult a(ItemStack itemstack, EntityHuman entityhuman, EntityLiving entityliving, EnumHand enumhand) { ++ return EnumInteractionResult.PASS; ++ } ++ ++ public String toString() { ++ return IRegistry.ITEM.getKey(this).getKey(); ++ } ++ ++ protected String m() { ++ if (this.name == null) { ++ this.name = SystemUtils.a("item", IRegistry.ITEM.getKey(this)); ++ } ++ ++ return this.name; ++ } ++ ++ public String getName() { ++ return this.m(); ++ } ++ ++ public String f(ItemStack itemstack) { ++ return this.getName(); ++ } ++ ++ public boolean n() { ++ return true; ++ } ++ ++ @Nullable ++ public final Item getCraftingRemainingItem() { ++ return this.craftingResult; ++ } ++ ++ public boolean p() { ++ return this.craftingResult != null; ++ } ++ ++ public void a(ItemStack itemstack, World world, Entity entity, int i, boolean flag) {} ++ ++ public void b(ItemStack itemstack, World world, EntityHuman entityhuman) {} ++ ++ public boolean ac_() { ++ return false; ++ } ++ ++ public EnumAnimation d_(ItemStack itemstack) { ++ return itemstack.getItem().isFood() ? EnumAnimation.EAT : EnumAnimation.NONE; ++ } ++ ++ public int e_(ItemStack itemstack) { ++ return itemstack.getItem().isFood() ? (this.getFoodInfo().e() ? 16 : 32) : 0; ++ } ++ ++ public void a(ItemStack itemstack, World world, EntityLiving entityliving, int i) {} ++ ++ public IChatBaseComponent h(ItemStack itemstack) { ++ return new ChatMessage(this.f(itemstack)); ++ } ++ ++ public boolean e(ItemStack itemstack) { ++ return itemstack.hasEnchantments(); ++ } ++ ++ public EnumItemRarity i(ItemStack itemstack) { ++ if (!itemstack.hasEnchantments()) { ++ return this.a; ++ } else { ++ switch (this.a) { ++ case COMMON: ++ case UNCOMMON: ++ return EnumItemRarity.RARE; ++ case RARE: ++ return EnumItemRarity.EPIC; ++ case EPIC: ++ default: ++ return this.a; ++ } ++ } ++ } ++ ++ public boolean f_(ItemStack itemstack) { ++ return this.getMaxStackSize() == 1 && this.usesDurability(); ++ } ++ ++ protected static MovingObjectPositionBlock a(World world, EntityHuman entityhuman, RayTrace.FluidCollisionOption raytrace_fluidcollisionoption) { ++ float f = entityhuman.pitch; ++ float f1 = entityhuman.yaw; ++ Vec3D vec3d = entityhuman.j(1.0F); ++ float f2 = MathHelper.cos(-f1 * 0.017453292F - 3.1415927F); ++ float f3 = MathHelper.sin(-f1 * 0.017453292F - 3.1415927F); ++ float f4 = -MathHelper.cos(-f * 0.017453292F); ++ float f5 = MathHelper.sin(-f * 0.017453292F); ++ float f6 = f3 * f4; ++ float f7 = f2 * f4; ++ double d0 = 5.0D; ++ Vec3D vec3d1 = vec3d.add((double) f6 * 5.0D, (double) f5 * 5.0D, (double) f7 * 5.0D); ++ ++ return world.rayTrace(new RayTrace(vec3d, vec3d1, RayTrace.BlockCollisionOption.OUTLINE, raytrace_fluidcollisionoption, entityhuman)); ++ } ++ ++ public int c() { ++ return 0; ++ } ++ ++ public void a(CreativeModeTab creativemodetab, NonNullList nonnulllist) { ++ if (this.a(creativemodetab)) { ++ nonnulllist.add(new ItemStack(this)); ++ } ++ ++ } ++ ++ protected boolean a(CreativeModeTab creativemodetab) { ++ CreativeModeTab creativemodetab1 = this.q(); ++ ++ return creativemodetab1 != null && (creativemodetab == CreativeModeTab.g || creativemodetab == creativemodetab1); ++ } ++ ++ @Nullable ++ public final CreativeModeTab q() { ++ return this.i; ++ } ++ ++ public boolean a(ItemStack itemstack, ItemStack itemstack1) { ++ return false; ++ } ++ ++ public Multimap a(EnumItemSlot enumitemslot) { ++ return ImmutableMultimap.of(); ++ } ++ ++ public boolean j(ItemStack itemstack) { ++ return itemstack.getItem() == Items.CROSSBOW; ++ } ++ ++ public ItemStack createItemStack() { ++ return new ItemStack(this); ++ } ++ ++ public boolean a(Tag tag) { ++ return tag.isTagged(this); ++ } ++ ++ public boolean isFood() { ++ return this.foodInfo != null; ++ } ++ ++ @Nullable ++ public FoodInfo getFoodInfo() { ++ return this.foodInfo; ++ } ++ ++ public SoundEffect ae_() { ++ return SoundEffects.ENTITY_GENERIC_DRINK; ++ } ++ ++ public SoundEffect ad_() { ++ return SoundEffects.ENTITY_GENERIC_EAT; ++ } ++ ++ public boolean u() { ++ return this.d; ++ } ++ ++ public boolean a(DamageSource damagesource) { ++ return !this.d || !damagesource.isFire(); ++ } ++ ++ public static class Info { ++ ++ private int a = 64; ++ private int b; ++ private Item c; ++ private CreativeModeTab d; ++ private EnumItemRarity e; ++ private FoodInfo f; ++ private boolean g; ++ ++ public Info() { ++ this.e = EnumItemRarity.COMMON; ++ } ++ ++ public Item.Info a(FoodInfo foodinfo) { ++ this.f = foodinfo; ++ return this; ++ } ++ ++ public Item.Info a(int i) { ++ if (this.b > 0) { ++ throw new RuntimeException("Unable to have damage AND stack."); ++ } else { ++ this.a = i; ++ return this; ++ } ++ } ++ ++ public Item.Info b(int i) { ++ return this.b == 0 ? this.c(i) : this; ++ } ++ ++ public Item.Info c(int i) { ++ this.b = i; ++ this.a = 1; ++ return this; ++ } ++ ++ public Item.Info a(Item item) { ++ this.c = item; ++ return this; ++ } ++ ++ public Item.Info a(CreativeModeTab creativemodetab) { ++ this.d = creativemodetab; ++ return this; ++ } ++ ++ public Item.Info a(EnumItemRarity enumitemrarity) { ++ this.e = enumitemrarity; ++ return this; ++ } ++ ++ public Item.Info a() { ++ this.g = true; ++ return this; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/item/ItemCooldownPlayer.java b/src/main/java/net/minecraft/world/item/ItemCooldownPlayer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5f70e39f4da2880a6f734a225be83061b00847c8 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/item/ItemCooldownPlayer.java +@@ -0,0 +1,25 @@ ++package net.minecraft.world.item; ++ ++import net.minecraft.network.protocol.game.PacketPlayOutSetCooldown; ++import net.minecraft.server.level.EntityPlayer; ++ ++public class ItemCooldownPlayer extends ItemCooldown { ++ ++ private final EntityPlayer a; ++ ++ public ItemCooldownPlayer(EntityPlayer entityplayer) { ++ this.a = entityplayer; ++ } ++ ++ @Override ++ protected void b(Item item, int i) { ++ super.b(item, i); ++ this.a.playerConnection.sendPacket(new PacketPlayOutSetCooldown(item, i)); ++ } ++ ++ @Override ++ protected void c(Item item) { ++ super.c(item); ++ this.a.playerConnection.sendPacket(new PacketPlayOutSetCooldown(item, 0)); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/item/ItemExpBottle.java b/src/main/java/net/minecraft/world/item/ItemExpBottle.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3f41fe5bf1a0cc283d6a72824779026fdad75708 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/item/ItemExpBottle.java +@@ -0,0 +1,43 @@ ++package net.minecraft.world.item; ++ ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.stats.StatisticList; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.InteractionResultWrapper; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.projectile.EntityThrownExpBottle; ++import net.minecraft.world.level.World; ++ ++public class ItemExpBottle extends Item { ++ ++ public ItemExpBottle(Item.Info item_info) { ++ super(item_info); ++ } ++ ++ @Override ++ public boolean e(ItemStack itemstack) { ++ return true; ++ } ++ ++ @Override ++ public InteractionResultWrapper a(World world, EntityHuman entityhuman, EnumHand enumhand) { ++ ItemStack itemstack = entityhuman.b(enumhand); ++ ++ world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_EXPERIENCE_BOTTLE_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemExpBottle.RANDOM.nextFloat() * 0.4F + 0.8F)); ++ if (!world.isClientSide) { ++ EntityThrownExpBottle entitythrownexpbottle = new EntityThrownExpBottle(world, entityhuman); ++ ++ entitythrownexpbottle.setItem(itemstack); ++ entitythrownexpbottle.a(entityhuman, entityhuman.pitch, entityhuman.yaw, -20.0F, 0.7F, 1.0F); ++ world.addEntity(entitythrownexpbottle); ++ } ++ ++ entityhuman.b(StatisticList.ITEM_USED.b(this)); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } ++ ++ return InteractionResultWrapper.a(itemstack, world.s_()); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/item/ItemFireworks.java b/src/main/java/net/minecraft/world/item/ItemFireworks.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9153945c2e245b9a2a098bdf58b0dcab052084ff +--- /dev/null ++++ b/src/main/java/net/minecraft/world/item/ItemFireworks.java +@@ -0,0 +1,77 @@ ++package net.minecraft.world.item; ++ ++import java.util.Arrays; ++import java.util.Comparator; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; ++import net.minecraft.world.InteractionResultWrapper; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.projectile.EntityFireworks; ++import net.minecraft.world.item.context.ItemActionContext; ++import net.minecraft.world.level.World; ++import net.minecraft.world.phys.Vec3D; ++ ++public class ItemFireworks extends Item { ++ ++ public ItemFireworks(Item.Info item_info) { ++ super(item_info); ++ } ++ ++ @Override ++ public EnumInteractionResult a(ItemActionContext itemactioncontext) { ++ World world = itemactioncontext.getWorld(); ++ ++ if (!world.isClientSide) { ++ ItemStack itemstack = itemactioncontext.getItemStack(); ++ Vec3D vec3d = itemactioncontext.getPos(); ++ EnumDirection enumdirection = itemactioncontext.getClickedFace(); ++ EntityFireworks entityfireworks = new EntityFireworks(world, itemactioncontext.getEntity(), vec3d.x + (double) enumdirection.getAdjacentX() * 0.15D, vec3d.y + (double) enumdirection.getAdjacentY() * 0.15D, vec3d.z + (double) enumdirection.getAdjacentZ() * 0.15D, itemstack); ++ ++ world.addEntity(entityfireworks); ++ itemstack.subtract(1); ++ } ++ ++ return EnumInteractionResult.a(world.isClientSide); ++ } ++ ++ @Override ++ public InteractionResultWrapper a(World world, EntityHuman entityhuman, EnumHand enumhand) { ++ if (entityhuman.isGliding()) { ++ ItemStack itemstack = entityhuman.b(enumhand); ++ ++ if (!world.isClientSide) { ++ world.addEntity(new EntityFireworks(world, itemstack, entityhuman)); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } ++ } ++ ++ return InteractionResultWrapper.a(entityhuman.b(enumhand), world.s_()); ++ } else { ++ return InteractionResultWrapper.pass(entityhuman.b(enumhand)); ++ } ++ } ++ ++ public static enum EffectType { ++ ++ SMALL_BALL(0, "small_ball"), LARGE_BALL(1, "large_ball"), STAR(2, "star"), CREEPER(3, "creeper"), BURST(4, "burst"); ++ ++ private static final ItemFireworks.EffectType[] f = (ItemFireworks.EffectType[]) Arrays.stream(values()).sorted(Comparator.comparingInt((itemfireworks_effecttype) -> { ++ return itemfireworks_effecttype.g; ++ })).toArray((i) -> { ++ return new ItemFireworks.EffectType[i]; ++ }); ++ private final int g; ++ private final String h; ++ ++ private EffectType(int i, String s) { ++ this.g = i; ++ this.h = s; ++ } ++ ++ public int a() { ++ return this.g; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/item/ItemLingeringPotion.java b/src/main/java/net/minecraft/world/item/ItemLingeringPotion.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a75f374f0639e8143772aa863666afe25d2020cf +--- /dev/null ++++ b/src/main/java/net/minecraft/world/item/ItemLingeringPotion.java +@@ -0,0 +1,21 @@ ++package net.minecraft.world.item; ++ ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.InteractionResultWrapper; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.level.World; ++ ++public class ItemLingeringPotion extends ItemPotionThrowable { ++ ++ public ItemLingeringPotion(Item.Info item_info) { ++ super(item_info); ++ } ++ ++ @Override ++ public InteractionResultWrapper a(World world, EntityHuman entityhuman, EnumHand enumhand) { ++ world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_LINGERING_POTION_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemLingeringPotion.RANDOM.nextFloat() * 0.4F + 0.8F)); ++ return super.a(world, entityhuman, enumhand); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/item/ItemNameTag.java b/src/main/java/net/minecraft/world/item/ItemNameTag.java +new file mode 100644 +index 0000000000000000000000000000000000000000..140a865f4f8fb3e4f787cf8d334d20fac6cb5eef +--- /dev/null ++++ b/src/main/java/net/minecraft/world/item/ItemNameTag.java +@@ -0,0 +1,32 @@ ++package net.minecraft.world.item; ++ ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.player.EntityHuman; ++ ++public class ItemNameTag extends Item { ++ ++ public ItemNameTag(Item.Info item_info) { ++ super(item_info); ++ } ++ ++ @Override ++ public EnumInteractionResult a(ItemStack itemstack, EntityHuman entityhuman, EntityLiving entityliving, EnumHand enumhand) { ++ if (itemstack.hasName() && !(entityliving instanceof EntityHuman)) { ++ if (!entityhuman.world.isClientSide && entityliving.isAlive()) { ++ entityliving.setCustomName(itemstack.getName()); ++ if (entityliving instanceof EntityInsentient) { ++ ((EntityInsentient) entityliving).setPersistent(); ++ } ++ ++ itemstack.subtract(1); ++ } ++ ++ return EnumInteractionResult.a(entityhuman.world.isClientSide); ++ } else { ++ return EnumInteractionResult.PASS; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/item/ItemPotionThrowable.java b/src/main/java/net/minecraft/world/item/ItemPotionThrowable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d050243946ad7023d5dd3958d7056cddcaf185a4 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/item/ItemPotionThrowable.java +@@ -0,0 +1,35 @@ ++package net.minecraft.world.item; ++ ++import net.minecraft.stats.StatisticList; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.InteractionResultWrapper; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.projectile.EntityPotion; ++import net.minecraft.world.level.World; ++ ++public class ItemPotionThrowable extends ItemPotion { ++ ++ public ItemPotionThrowable(Item.Info item_info) { ++ super(item_info); ++ } ++ ++ @Override ++ public InteractionResultWrapper a(World world, EntityHuman entityhuman, EnumHand enumhand) { ++ ItemStack itemstack = entityhuman.b(enumhand); ++ ++ if (!world.isClientSide) { ++ EntityPotion entitypotion = new EntityPotion(world, entityhuman); ++ ++ entitypotion.setItem(itemstack); ++ entitypotion.a(entityhuman, entityhuman.pitch, entityhuman.yaw, -20.0F, 0.5F, 1.0F); ++ world.addEntity(entitypotion); ++ } ++ ++ entityhuman.b(StatisticList.ITEM_USED.b(this)); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } ++ ++ return InteractionResultWrapper.a(itemstack, world.s_()); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/item/ItemSplashPotion.java b/src/main/java/net/minecraft/world/item/ItemSplashPotion.java +new file mode 100644 +index 0000000000000000000000000000000000000000..98f29fac4bf087ad15f1cc7e85b408e22ec07efd +--- /dev/null ++++ b/src/main/java/net/minecraft/world/item/ItemSplashPotion.java +@@ -0,0 +1,21 @@ ++package net.minecraft.world.item; ++ ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.InteractionResultWrapper; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.level.World; ++ ++public class ItemSplashPotion extends ItemPotionThrowable { ++ ++ public ItemSplashPotion(Item.Info item_info) { ++ super(item_info); ++ } ++ ++ @Override ++ public InteractionResultWrapper a(World world, EntityHuman entityhuman, EnumHand enumhand) { ++ world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_SPLASH_POTION_THROW, SoundCategory.PLAYERS, 0.5F, 0.4F / (ItemSplashPotion.RANDOM.nextFloat() * 0.4F + 0.8F)); ++ return super.a(world, entityhuman, enumhand); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/item/alchemy/PotionUtil.java b/src/main/java/net/minecraft/world/item/alchemy/PotionUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..daad63e731008eddccd3f51418a2a9b2d587f77b +--- /dev/null ++++ b/src/main/java/net/minecraft/world/item/alchemy/PotionUtil.java +@@ -0,0 +1,154 @@ ++package net.minecraft.world.item.alchemy; ++ ++import com.google.common.collect.Lists; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.List; ++import javax.annotation.Nullable; ++import net.minecraft.EnumChatFormat; ++import net.minecraft.core.IRegistry; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.network.chat.ChatMessage; ++import net.minecraft.network.chat.IChatMutableComponent; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.world.effect.MobEffect; ++import net.minecraft.world.item.ItemStack; ++ ++public class PotionUtil { ++ ++ private static final IChatMutableComponent a = (new ChatMessage("effect.none")).a(EnumChatFormat.GRAY); ++ ++ public static List getEffects(ItemStack itemstack) { ++ return a(itemstack.getTag()); ++ } ++ ++ public static List a(PotionRegistry potionregistry, Collection collection) { ++ List list = Lists.newArrayList(); ++ ++ list.addAll(potionregistry.a()); ++ list.addAll(collection); ++ return list; ++ } ++ ++ public static List a(@Nullable NBTTagCompound nbttagcompound) { ++ List list = Lists.newArrayList(); ++ ++ list.addAll(c(nbttagcompound).a()); ++ a(nbttagcompound, (List) list); ++ return list; ++ } ++ ++ public static List b(ItemStack itemstack) { ++ return b(itemstack.getTag()); ++ } ++ ++ public static List b(@Nullable NBTTagCompound nbttagcompound) { ++ List list = Lists.newArrayList(); ++ ++ a(nbttagcompound, (List) list); ++ return list; ++ } ++ ++ public static void a(@Nullable NBTTagCompound nbttagcompound, List list) { ++ if (nbttagcompound != null && nbttagcompound.hasKeyOfType("CustomPotionEffects", 9)) { ++ NBTTagList nbttaglist = nbttagcompound.getList("CustomPotionEffects", 10); ++ ++ for (int i = 0; i < nbttaglist.size(); ++i) { ++ NBTTagCompound nbttagcompound1 = nbttaglist.getCompound(i); ++ MobEffect mobeffect = MobEffect.b(nbttagcompound1); ++ ++ if (mobeffect != null) { ++ list.add(mobeffect); ++ } ++ } ++ } ++ ++ } ++ ++ public static int c(ItemStack itemstack) { ++ NBTTagCompound nbttagcompound = itemstack.getTag(); ++ ++ return nbttagcompound != null && nbttagcompound.hasKeyOfType("CustomPotionColor", 99) ? nbttagcompound.getInt("CustomPotionColor") : (d(itemstack) == Potions.EMPTY ? 16253176 : a((Collection) getEffects(itemstack))); ++ } ++ ++ public static int a(PotionRegistry potionregistry) { ++ return potionregistry == Potions.EMPTY ? 16253176 : a((Collection) potionregistry.a()); ++ } ++ ++ public static int a(Collection collection) { ++ int i = 3694022; ++ ++ if (collection.isEmpty()) { ++ return 3694022; ++ } else { ++ float f = 0.0F; ++ float f1 = 0.0F; ++ float f2 = 0.0F; ++ int j = 0; ++ Iterator iterator = collection.iterator(); ++ ++ while (iterator.hasNext()) { ++ MobEffect mobeffect = (MobEffect) iterator.next(); ++ ++ if (mobeffect.isShowParticles()) { ++ int k = mobeffect.getMobEffect().getColor(); ++ int l = mobeffect.getAmplifier() + 1; ++ ++ f += (float) (l * (k >> 16 & 255)) / 255.0F; ++ f1 += (float) (l * (k >> 8 & 255)) / 255.0F; ++ f2 += (float) (l * (k >> 0 & 255)) / 255.0F; ++ j += l; ++ } ++ } ++ ++ if (j == 0) { ++ return 0; ++ } else { ++ f = f / (float) j * 255.0F; ++ f1 = f1 / (float) j * 255.0F; ++ f2 = f2 / (float) j * 255.0F; ++ return (int) f << 16 | (int) f1 << 8 | (int) f2; ++ } ++ } ++ } ++ ++ public static PotionRegistry d(ItemStack itemstack) { ++ return c(itemstack.getTag()); ++ } ++ ++ public static PotionRegistry c(@Nullable NBTTagCompound nbttagcompound) { ++ return nbttagcompound == null ? Potions.EMPTY : PotionRegistry.a(nbttagcompound.getString("Potion")); ++ } ++ ++ public static ItemStack a(ItemStack itemstack, PotionRegistry potionregistry) { ++ MinecraftKey minecraftkey = IRegistry.POTION.getKey(potionregistry); ++ ++ if (potionregistry == Potions.EMPTY) { ++ itemstack.removeTag("Potion"); ++ } else { ++ itemstack.getOrCreateTag().setString("Potion", minecraftkey.toString()); ++ } ++ ++ return itemstack; ++ } ++ ++ public static ItemStack a(ItemStack itemstack, Collection collection) { ++ if (collection.isEmpty()) { ++ return itemstack; ++ } else { ++ NBTTagCompound nbttagcompound = itemstack.getOrCreateTag(); ++ NBTTagList nbttaglist = nbttagcompound.getList("CustomPotionEffects", 9); ++ Iterator iterator = collection.iterator(); ++ ++ while (iterator.hasNext()) { ++ MobEffect mobeffect = (MobEffect) iterator.next(); ++ ++ nbttaglist.add(mobeffect.a(new NBTTagCompound())); ++ } ++ ++ nbttagcompound.set("CustomPotionEffects", nbttaglist); ++ return itemstack; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0134bbda9e6fc900b7eefa05442e25539bab3431 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java +@@ -0,0 +1,151 @@ ++package net.minecraft.world.item.enchantment; ++ ++import com.google.common.collect.Maps; ++import java.util.Map; ++import javax.annotation.Nullable; ++import net.minecraft.EnumChatFormat; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.IRegistry; ++import net.minecraft.network.chat.ChatMessage; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EnumItemSlot; ++import net.minecraft.world.entity.EnumMonsterType; ++import net.minecraft.world.item.ItemStack; ++ ++public abstract class Enchantment { ++ ++ private final EnumItemSlot[] a; ++ private final Enchantment.Rarity d; ++ public final EnchantmentSlotType itemTarget; ++ @Nullable ++ protected String c; ++ ++ protected Enchantment(Enchantment.Rarity enchantment_rarity, EnchantmentSlotType enchantmentslottype, EnumItemSlot[] aenumitemslot) { ++ this.d = enchantment_rarity; ++ this.itemTarget = enchantmentslottype; ++ this.a = aenumitemslot; ++ } ++ ++ public Map a(EntityLiving entityliving) { ++ Map map = Maps.newEnumMap(EnumItemSlot.class); ++ EnumItemSlot[] aenumitemslot = this.a; ++ int i = aenumitemslot.length; ++ ++ for (int j = 0; j < i; ++j) { ++ EnumItemSlot enumitemslot = aenumitemslot[j]; ++ ItemStack itemstack = entityliving.getEquipment(enumitemslot); ++ ++ if (!itemstack.isEmpty()) { ++ map.put(enumitemslot, itemstack); ++ } ++ } ++ ++ return map; ++ } ++ ++ public Enchantment.Rarity d() { ++ return this.d; ++ } ++ ++ public int getStartLevel() { ++ return 1; ++ } ++ ++ public int getMaxLevel() { ++ return 1; ++ } ++ ++ public int a(int i) { ++ return 1 + i * 10; ++ } ++ ++ public int b(int i) { ++ return this.a(i) + 5; ++ } ++ ++ public int a(int i, DamageSource damagesource) { ++ return 0; ++ } ++ ++ public float a(int i, EnumMonsterType enummonstertype) { ++ return 0.0F; ++ } ++ ++ public final boolean isCompatible(Enchantment enchantment) { ++ return this.a(enchantment) && enchantment.a(this); ++ } ++ ++ protected boolean a(Enchantment enchantment) { ++ return this != enchantment; ++ } ++ ++ protected String f() { ++ if (this.c == null) { ++ this.c = SystemUtils.a("enchantment", IRegistry.ENCHANTMENT.getKey(this)); ++ } ++ ++ return this.c; ++ } ++ ++ public String g() { ++ return this.f(); ++ } ++ ++ public IChatBaseComponent d(int i) { ++ ChatMessage chatmessage = new ChatMessage(this.g()); ++ ++ if (this.c()) { ++ chatmessage.a(EnumChatFormat.RED); ++ } else { ++ chatmessage.a(EnumChatFormat.GRAY); ++ } ++ ++ if (i != 1 || this.getMaxLevel() != 1) { ++ chatmessage.c(" ").addSibling(new ChatMessage("enchantment.level." + i)); ++ } ++ ++ return chatmessage; ++ } ++ ++ public boolean canEnchant(ItemStack itemstack) { ++ return this.itemTarget.canEnchant(itemstack.getItem()); ++ } ++ ++ public void a(EntityLiving entityliving, Entity entity, int i) {} ++ ++ public void b(EntityLiving entityliving, Entity entity, int i) {} ++ ++ public boolean isTreasure() { ++ return false; ++ } ++ ++ public boolean c() { ++ return false; ++ } ++ ++ public boolean h() { ++ return true; ++ } ++ ++ public boolean i() { ++ return true; ++ } ++ ++ public static enum Rarity { ++ ++ COMMON(10), UNCOMMON(5), RARE(2), VERY_RARE(1); ++ ++ private final int e; ++ ++ private Rarity(int i) { ++ this.e = i; ++ } ++ ++ public int a() { ++ return this.e; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentManager.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d313b02f41e4f4a90676cbb37afce4e92dd4d664 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentManager.java +@@ -0,0 +1,428 @@ ++package net.minecraft.world.item.enchantment; ++ ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Map.Entry; ++import java.util.Random; ++import java.util.function.Predicate; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.IRegistry; ++import net.minecraft.nbt.NBTBase; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.util.MathHelper; ++import net.minecraft.util.WeightedRandom; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EnumItemSlot; ++import net.minecraft.world.entity.EnumMonsterType; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.item.ItemEnchantedBook; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import org.apache.commons.lang3.mutable.MutableFloat; ++import org.apache.commons.lang3.mutable.MutableInt; ++ ++public class EnchantmentManager { ++ ++ public static int getEnchantmentLevel(Enchantment enchantment, ItemStack itemstack) { ++ if (itemstack.isEmpty()) { ++ return 0; ++ } else { ++ MinecraftKey minecraftkey = IRegistry.ENCHANTMENT.getKey(enchantment); ++ NBTTagList nbttaglist = itemstack.getEnchantments(); ++ ++ for (int i = 0; i < nbttaglist.size(); ++i) { ++ NBTTagCompound nbttagcompound = nbttaglist.getCompound(i); ++ MinecraftKey minecraftkey1 = MinecraftKey.a(nbttagcompound.getString("id")); ++ ++ if (minecraftkey1 != null && minecraftkey1.equals(minecraftkey)) { ++ return MathHelper.clamp(nbttagcompound.getInt("lvl"), 0, 255); ++ } ++ } ++ ++ return 0; ++ } ++ } ++ ++ public static Map a(ItemStack itemstack) { ++ NBTTagList nbttaglist = itemstack.getItem() == Items.ENCHANTED_BOOK ? ItemEnchantedBook.d(itemstack) : itemstack.getEnchantments(); ++ ++ return a(nbttaglist); ++ } ++ ++ public static Map a(NBTTagList nbttaglist) { ++ Map map = Maps.newLinkedHashMap(); ++ ++ for (int i = 0; i < nbttaglist.size(); ++i) { ++ NBTTagCompound nbttagcompound = nbttaglist.getCompound(i); ++ ++ IRegistry.ENCHANTMENT.getOptional(MinecraftKey.a(nbttagcompound.getString("id"))).ifPresent((enchantment) -> { ++ Integer integer = (Integer) map.put(enchantment, nbttagcompound.getInt("lvl")); ++ }); ++ } ++ ++ return map; ++ } ++ ++ public static void a(Map map, ItemStack itemstack) { ++ NBTTagList nbttaglist = new NBTTagList(); ++ Iterator iterator = map.entrySet().iterator(); ++ ++ while (iterator.hasNext()) { ++ Entry entry = (Entry) iterator.next(); ++ Enchantment enchantment = (Enchantment) entry.getKey(); ++ ++ if (enchantment != null) { ++ int i = (Integer) entry.getValue(); ++ NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ ++ nbttagcompound.setString("id", String.valueOf(IRegistry.ENCHANTMENT.getKey(enchantment))); ++ nbttagcompound.setShort("lvl", (short) i); ++ nbttaglist.add(nbttagcompound); ++ if (itemstack.getItem() == Items.ENCHANTED_BOOK) { ++ ItemEnchantedBook.a(itemstack, new WeightedRandomEnchant(enchantment, i)); ++ } ++ } ++ } ++ ++ if (nbttaglist.isEmpty()) { ++ itemstack.removeTag("Enchantments"); ++ } else if (itemstack.getItem() != Items.ENCHANTED_BOOK) { ++ itemstack.a("Enchantments", (NBTBase) nbttaglist); ++ } ++ ++ } ++ ++ private static void a(EnchantmentManager.a enchantmentmanager_a, ItemStack itemstack) { ++ if (!itemstack.isEmpty()) { ++ NBTTagList nbttaglist = itemstack.getEnchantments(); ++ ++ for (int i = 0; i < nbttaglist.size(); ++i) { ++ String s = nbttaglist.getCompound(i).getString("id"); ++ int j = nbttaglist.getCompound(i).getInt("lvl"); ++ ++ IRegistry.ENCHANTMENT.getOptional(MinecraftKey.a(s)).ifPresent((enchantment) -> { ++ enchantmentmanager_a.accept(enchantment, j); ++ }); ++ } ++ ++ } ++ } ++ ++ private static void a(EnchantmentManager.a enchantmentmanager_a, Iterable iterable) { ++ Iterator iterator = iterable.iterator(); ++ ++ while (iterator.hasNext()) { ++ ItemStack itemstack = (ItemStack) iterator.next(); ++ ++ a(enchantmentmanager_a, itemstack); ++ } ++ ++ } ++ ++ public static int a(Iterable iterable, DamageSource damagesource) { ++ MutableInt mutableint = new MutableInt(); ++ ++ a((enchantment, i) -> { ++ mutableint.add(enchantment.a(i, damagesource)); ++ }, iterable); ++ return mutableint.intValue(); ++ } ++ ++ public static float a(ItemStack itemstack, EnumMonsterType enummonstertype) { ++ MutableFloat mutablefloat = new MutableFloat(); ++ ++ a((enchantment, i) -> { ++ mutablefloat.add(enchantment.a(i, enummonstertype)); ++ }, itemstack); ++ return mutablefloat.floatValue(); ++ } ++ ++ public static float a(EntityLiving entityliving) { ++ int i = a(Enchantments.SWEEPING, entityliving); ++ ++ return i > 0 ? EnchantmentSweeping.e(i) : 0.0F; ++ } ++ ++ public static void a(EntityLiving entityliving, Entity entity) { ++ EnchantmentManager.a enchantmentmanager_a = (enchantment, i) -> { ++ enchantment.b(entityliving, entity, i); ++ }; ++ ++ if (entityliving != null) { ++ a(enchantmentmanager_a, entityliving.bp()); ++ } ++ ++ if (entity instanceof EntityHuman) { ++ a(enchantmentmanager_a, entityliving.getItemInMainHand()); ++ } ++ ++ } ++ ++ public static void b(EntityLiving entityliving, Entity entity) { ++ EnchantmentManager.a enchantmentmanager_a = (enchantment, i) -> { ++ enchantment.a(entityliving, entity, i); ++ }; ++ ++ if (entityliving != null) { ++ a(enchantmentmanager_a, entityliving.bp()); ++ } ++ ++ if (entityliving instanceof EntityHuman) { ++ a(enchantmentmanager_a, entityliving.getItemInMainHand()); ++ } ++ ++ } ++ ++ public static int a(Enchantment enchantment, EntityLiving entityliving) { ++ Iterable iterable = enchantment.a(entityliving).values(); ++ ++ if (iterable == null) { ++ return 0; ++ } else { ++ int i = 0; ++ Iterator iterator = iterable.iterator(); ++ ++ while (iterator.hasNext()) { ++ ItemStack itemstack = (ItemStack) iterator.next(); ++ int j = getEnchantmentLevel(enchantment, itemstack); ++ ++ if (j > i) { ++ i = j; ++ } ++ } ++ ++ return i; ++ } ++ } ++ ++ public static int b(EntityLiving entityliving) { ++ return a(Enchantments.KNOCKBACK, entityliving); ++ } ++ ++ public static int getFireAspectEnchantmentLevel(EntityLiving entityliving) { ++ return a(Enchantments.FIRE_ASPECT, entityliving); ++ } ++ ++ public static int getOxygenEnchantmentLevel(EntityLiving entityliving) { ++ return a(Enchantments.OXYGEN, entityliving); ++ } ++ ++ public static int e(EntityLiving entityliving) { ++ return a(Enchantments.DEPTH_STRIDER, entityliving); ++ } ++ ++ public static int getDigSpeedEnchantmentLevel(EntityLiving entityliving) { ++ return a(Enchantments.DIG_SPEED, entityliving); ++ } ++ ++ public static int b(ItemStack itemstack) { ++ return getEnchantmentLevel(Enchantments.LUCK, itemstack); ++ } ++ ++ public static int c(ItemStack itemstack) { ++ return getEnchantmentLevel(Enchantments.LURE, itemstack); ++ } ++ ++ public static int g(EntityLiving entityliving) { ++ return a(Enchantments.LOOT_BONUS_MOBS, entityliving); ++ } ++ ++ public static boolean h(EntityLiving entityliving) { ++ return a(Enchantments.WATER_WORKER, entityliving) > 0; ++ } ++ ++ public static boolean i(EntityLiving entityliving) { ++ return a(Enchantments.FROST_WALKER, entityliving) > 0; ++ } ++ ++ public static boolean j(EntityLiving entityliving) { ++ return a(Enchantments.SOUL_SPEED, entityliving) > 0; ++ } ++ ++ public static boolean d(ItemStack itemstack) { ++ return getEnchantmentLevel(Enchantments.BINDING_CURSE, itemstack) > 0; ++ } ++ ++ public static boolean shouldNotDrop(ItemStack itemstack) { ++ return getEnchantmentLevel(Enchantments.VANISHING_CURSE, itemstack) > 0; ++ } ++ ++ public static int f(ItemStack itemstack) { ++ return getEnchantmentLevel(Enchantments.LOYALTY, itemstack); ++ } ++ ++ public static int g(ItemStack itemstack) { ++ return getEnchantmentLevel(Enchantments.RIPTIDE, itemstack); ++ } ++ ++ public static boolean h(ItemStack itemstack) { ++ return getEnchantmentLevel(Enchantments.CHANNELING, itemstack) > 0; ++ } ++ ++ @Nullable ++ public static Entry b(Enchantment enchantment, EntityLiving entityliving) { ++ return a(enchantment, entityliving, (itemstack) -> { ++ return true; ++ }); ++ } ++ ++ @Nullable ++ public static Entry a(Enchantment enchantment, EntityLiving entityliving, Predicate predicate) { ++ Map map = enchantment.a(entityliving); ++ ++ if (map.isEmpty()) { ++ return null; ++ } else { ++ List> list = Lists.newArrayList(); ++ Iterator iterator = map.entrySet().iterator(); ++ ++ while (iterator.hasNext()) { ++ Entry entry = (Entry) iterator.next(); ++ ItemStack itemstack = (ItemStack) entry.getValue(); ++ ++ if (!itemstack.isEmpty() && getEnchantmentLevel(enchantment, itemstack) > 0 && predicate.test(itemstack)) { ++ list.add(entry); ++ } ++ } ++ ++ return list.isEmpty() ? null : (Entry) list.get(entityliving.getRandom().nextInt(list.size())); ++ } ++ } ++ ++ public static int a(Random random, int i, int j, ItemStack itemstack) { ++ Item item = itemstack.getItem(); ++ int k = item.c(); ++ ++ if (k <= 0) { ++ return 0; ++ } else { ++ if (j > 15) { ++ j = 15; ++ } ++ ++ int l = random.nextInt(8) + 1 + (j >> 1) + random.nextInt(j + 1); ++ ++ return i == 0 ? Math.max(l / 3, 1) : (i == 1 ? l * 2 / 3 + 1 : Math.max(l, j * 2)); ++ } ++ } ++ ++ public static ItemStack a(Random random, ItemStack itemstack, int i, boolean flag) { ++ List list = b(random, itemstack, i, flag); ++ boolean flag1 = itemstack.getItem() == Items.BOOK; ++ ++ if (flag1) { ++ itemstack = new ItemStack(Items.ENCHANTED_BOOK); ++ } ++ ++ Iterator iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ WeightedRandomEnchant weightedrandomenchant = (WeightedRandomEnchant) iterator.next(); ++ ++ if (flag1) { ++ ItemEnchantedBook.a(itemstack, weightedrandomenchant); ++ } else { ++ itemstack.addEnchantment(weightedrandomenchant.enchantment, weightedrandomenchant.level); ++ } ++ } ++ ++ return itemstack; ++ } ++ ++ public static List b(Random random, ItemStack itemstack, int i, boolean flag) { ++ List list = Lists.newArrayList(); ++ Item item = itemstack.getItem(); ++ int j = item.c(); ++ ++ if (j <= 0) { ++ return list; ++ } else { ++ i += 1 + random.nextInt(j / 4 + 1) + random.nextInt(j / 4 + 1); ++ float f = (random.nextFloat() + random.nextFloat() - 1.0F) * 0.15F; ++ ++ i = MathHelper.clamp(Math.round((float) i + (float) i * f), 1, Integer.MAX_VALUE); ++ List list1 = a(i, itemstack, flag); ++ ++ if (!list1.isEmpty()) { ++ list.add(WeightedRandom.a(random, list1)); ++ ++ while (random.nextInt(50) <= i) { ++ a(list1, (WeightedRandomEnchant) SystemUtils.a((List) list)); ++ if (list1.isEmpty()) { ++ break; ++ } ++ ++ list.add(WeightedRandom.a(random, list1)); ++ i /= 2; ++ } ++ } ++ ++ return list; ++ } ++ } ++ ++ public static void a(List list, WeightedRandomEnchant weightedrandomenchant) { ++ Iterator iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ if (!weightedrandomenchant.enchantment.isCompatible(((WeightedRandomEnchant) iterator.next()).enchantment)) { ++ iterator.remove(); ++ } ++ } ++ ++ } ++ ++ public static boolean a(Collection collection, Enchantment enchantment) { ++ Iterator iterator = collection.iterator(); ++ ++ Enchantment enchantment1; ++ ++ do { ++ if (!iterator.hasNext()) { ++ return true; ++ } ++ ++ enchantment1 = (Enchantment) iterator.next(); ++ } while (enchantment1.isCompatible(enchantment)); ++ ++ return false; ++ } ++ ++ public static List a(int i, ItemStack itemstack, boolean flag) { ++ List list = Lists.newArrayList(); ++ Item item = itemstack.getItem(); ++ boolean flag1 = itemstack.getItem() == Items.BOOK; ++ Iterator iterator = IRegistry.ENCHANTMENT.iterator(); ++ ++ while (iterator.hasNext()) { ++ Enchantment enchantment = (Enchantment) iterator.next(); ++ ++ if ((!enchantment.isTreasure() || flag) && enchantment.i() && (enchantment.itemTarget.canEnchant(item) || flag1)) { ++ for (int j = enchantment.getMaxLevel(); j > enchantment.getStartLevel() - 1; --j) { ++ if (i >= enchantment.a(j) && i <= enchantment.b(j)) { ++ list.add(new WeightedRandomEnchant(enchantment, j)); ++ break; ++ } ++ } ++ } ++ } ++ ++ return list; ++ } ++ ++ @FunctionalInterface ++ interface a { ++ ++ void accept(Enchantment enchantment, int i); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/BlockAccessAir.java b/src/main/java/net/minecraft/world/level/BlockAccessAir.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5f8022745f709b6d542182d2ac94147aefdd3f0f +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/BlockAccessAir.java +@@ -0,0 +1,32 @@ ++package net.minecraft.world.level; ++ ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.level.material.FluidTypes; ++ ++public enum BlockAccessAir implements IBlockAccess { ++ ++ INSTANCE; ++ ++ private BlockAccessAir() {} ++ ++ @Nullable ++ @Override ++ public TileEntity getTileEntity(BlockPosition blockposition) { ++ return null; ++ } ++ ++ @Override ++ public IBlockData getType(BlockPosition blockposition) { ++ return Blocks.AIR.getBlockData(); ++ } ++ ++ @Override ++ public Fluid getFluid(BlockPosition blockposition) { ++ return FluidTypes.EMPTY.h(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/ChunkCache.java b/src/main/java/net/minecraft/world/level/ChunkCache.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8541e87a34612e8bc86cf5c291164e091641d1af +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/ChunkCache.java +@@ -0,0 +1,128 @@ ++package net.minecraft.world.level; ++ ++import java.util.function.Predicate; ++import java.util.stream.Stream; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.border.WorldBorder; ++import net.minecraft.world.level.chunk.ChunkEmpty; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.chunk.IChunkProvider; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.level.material.FluidTypes; ++import net.minecraft.world.phys.AxisAlignedBB; ++import net.minecraft.world.phys.shapes.VoxelShape; ++ ++public class ChunkCache implements IBlockAccess, ICollisionAccess { ++ ++ protected final int a; ++ protected final int b; ++ protected final IChunkAccess[][] c; ++ protected boolean d; ++ protected final World e; ++ ++ public ChunkCache(World world, BlockPosition blockposition, BlockPosition blockposition1) { ++ this.e = world; ++ this.a = blockposition.getX() >> 4; ++ this.b = blockposition.getZ() >> 4; ++ int i = blockposition1.getX() >> 4; ++ int j = blockposition1.getZ() >> 4; ++ ++ this.c = new IChunkAccess[i - this.a + 1][j - this.b + 1]; ++ IChunkProvider ichunkprovider = world.getChunkProvider(); ++ ++ this.d = true; ++ ++ int k; ++ int l; ++ ++ for (k = this.a; k <= i; ++k) { ++ for (l = this.b; l <= j; ++l) { ++ this.c[k - this.a][l - this.b] = ichunkprovider.a(k, l); ++ } ++ } ++ ++ for (k = blockposition.getX() >> 4; k <= blockposition1.getX() >> 4; ++k) { ++ for (l = blockposition.getZ() >> 4; l <= blockposition1.getZ() >> 4; ++l) { ++ IChunkAccess ichunkaccess = this.c[k - this.a][l - this.b]; ++ ++ if (ichunkaccess != null && !ichunkaccess.a(blockposition.getY(), blockposition1.getY())) { ++ this.d = false; ++ return; ++ } ++ } ++ } ++ ++ } ++ ++ private IChunkAccess d(BlockPosition blockposition) { ++ return this.a(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ } ++ ++ private IChunkAccess a(int i, int j) { ++ int k = i - this.a; ++ int l = j - this.b; ++ ++ if (k >= 0 && k < this.c.length && l >= 0 && l < this.c[k].length) { ++ IChunkAccess ichunkaccess = this.c[k][l]; ++ ++ return (IChunkAccess) (ichunkaccess != null ? ichunkaccess : new ChunkEmpty(this.e, new ChunkCoordIntPair(i, j))); ++ } else { ++ return new ChunkEmpty(this.e, new ChunkCoordIntPair(i, j)); ++ } ++ } ++ ++ @Override ++ public WorldBorder getWorldBorder() { ++ return this.e.getWorldBorder(); ++ } ++ ++ @Override ++ public IBlockAccess c(int i, int j) { ++ return this.a(i, j); ++ } ++ ++ @Nullable ++ @Override ++ public TileEntity getTileEntity(BlockPosition blockposition) { ++ IChunkAccess ichunkaccess = this.d(blockposition); ++ ++ return ichunkaccess.getTileEntity(blockposition); ++ } ++ ++ @Override ++ public IBlockData getType(BlockPosition blockposition) { ++ if (World.isOutsideWorld(blockposition)) { ++ return Blocks.AIR.getBlockData(); ++ } else { ++ IChunkAccess ichunkaccess = this.d(blockposition); ++ ++ return ichunkaccess.getType(blockposition); ++ } ++ } ++ ++ @Override ++ public Stream c(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ return Stream.empty(); ++ } ++ ++ @Override ++ public Stream d(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ return this.b(entity, axisalignedbb); ++ } ++ ++ @Override ++ public Fluid getFluid(BlockPosition blockposition) { ++ if (World.isOutsideWorld(blockposition)) { ++ return FluidTypes.EMPTY.h(); ++ } else { ++ IChunkAccess ichunkaccess = this.d(blockposition); ++ ++ return ichunkaccess.getFluid(blockposition); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java b/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java +new file mode 100644 +index 0000000000000000000000000000000000000000..14e55bf842e928d1e8e2137f9efdef0f7c336362 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java +@@ -0,0 +1,147 @@ ++package net.minecraft.world.level; ++ ++import java.util.Spliterators.AbstractSpliterator; ++import java.util.function.Consumer; ++import java.util.stream.Stream; ++import java.util.stream.StreamSupport; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++ ++public class ChunkCoordIntPair { ++ ++ public static final long a = pair(1875016, 1875016); ++ public final int x; ++ public final int z; ++ ++ public ChunkCoordIntPair(int i, int j) { ++ this.x = i; ++ this.z = j; ++ } ++ ++ public ChunkCoordIntPair(BlockPosition blockposition) { ++ this.x = blockposition.getX() >> 4; ++ this.z = blockposition.getZ() >> 4; ++ } ++ ++ public ChunkCoordIntPair(long i) { ++ this.x = (int) i; ++ this.z = (int) (i >> 32); ++ } ++ ++ public long pair() { ++ return pair(this.x, this.z); ++ } ++ ++ public static long pair(int i, int j) { ++ return (long) i & 4294967295L | ((long) j & 4294967295L) << 32; ++ } ++ ++ public static int getX(long i) { ++ return (int) (i & 4294967295L); ++ } ++ ++ public static int getZ(long i) { ++ return (int) (i >>> 32 & 4294967295L); ++ } ++ ++ public int hashCode() { ++ int i = 1664525 * this.x + 1013904223; ++ int j = 1664525 * (this.z ^ -559038737) + 1013904223; ++ ++ return i ^ j; ++ } ++ ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } else if (!(object instanceof ChunkCoordIntPair)) { ++ return false; ++ } else { ++ ChunkCoordIntPair chunkcoordintpair = (ChunkCoordIntPair) object; ++ ++ return this.x == chunkcoordintpair.x && this.z == chunkcoordintpair.z; ++ } ++ } ++ ++ public int d() { ++ return this.x << 4; ++ } ++ ++ public int e() { ++ return this.z << 4; ++ } ++ ++ public int f() { ++ return (this.x << 4) + 15; ++ } ++ ++ public int g() { ++ return (this.z << 4) + 15; ++ } ++ ++ public int getRegionX() { ++ return this.x >> 5; ++ } ++ ++ public int getRegionZ() { ++ return this.z >> 5; ++ } ++ ++ public int j() { ++ return this.x & 31; ++ } ++ ++ public int k() { ++ return this.z & 31; ++ } ++ ++ public String toString() { ++ return "[" + this.x + ", " + this.z + "]"; ++ } ++ ++ public BlockPosition l() { ++ return new BlockPosition(this.d(), 0, this.e()); ++ } ++ ++ public int a(ChunkCoordIntPair chunkcoordintpair) { ++ return Math.max(Math.abs(this.x - chunkcoordintpair.x), Math.abs(this.z - chunkcoordintpair.z)); ++ } ++ ++ public static Stream a(ChunkCoordIntPair chunkcoordintpair, int i) { ++ return a(new ChunkCoordIntPair(chunkcoordintpair.x - i, chunkcoordintpair.z - i), new ChunkCoordIntPair(chunkcoordintpair.x + i, chunkcoordintpair.z + i)); ++ } ++ ++ public static Stream a(final ChunkCoordIntPair chunkcoordintpair, final ChunkCoordIntPair chunkcoordintpair1) { ++ int i = Math.abs(chunkcoordintpair.x - chunkcoordintpair1.x) + 1; ++ int j = Math.abs(chunkcoordintpair.z - chunkcoordintpair1.z) + 1; ++ final int k = chunkcoordintpair.x < chunkcoordintpair1.x ? 1 : -1; ++ final int l = chunkcoordintpair.z < chunkcoordintpair1.z ? 1 : -1; ++ ++ return StreamSupport.stream(new AbstractSpliterator((long) (i * j), 64) { ++ @Nullable ++ private ChunkCoordIntPair e; ++ ++ public boolean tryAdvance(Consumer consumer) { ++ if (this.e == null) { ++ this.e = chunkcoordintpair; ++ } else { ++ int i1 = this.e.x; ++ int j1 = this.e.z; ++ ++ if (i1 == chunkcoordintpair1.x) { ++ if (j1 == chunkcoordintpair1.z) { ++ return false; ++ } ++ ++ this.e = new ChunkCoordIntPair(chunkcoordintpair.x, j1 + l); ++ } else { ++ this.e = new ChunkCoordIntPair(i1 + k, j1); ++ } ++ } ++ ++ consumer.accept(this.e); ++ return true; ++ } ++ }, false); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/ICollisionAccess.java b/src/main/java/net/minecraft/world/level/ICollisionAccess.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fcf6cc86e3b5d9afe3ab3b3fba2ec13846ed0b4c +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/ICollisionAccess.java +@@ -0,0 +1,73 @@ ++package net.minecraft.world.level; ++ ++import java.util.function.BiPredicate; ++import java.util.function.Predicate; ++import java.util.stream.Stream; ++import java.util.stream.StreamSupport; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.border.WorldBorder; ++import net.minecraft.world.phys.AxisAlignedBB; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapeCollision; ++import net.minecraft.world.phys.shapes.VoxelShapes; ++ ++public interface ICollisionAccess extends IBlockAccess { ++ ++ WorldBorder getWorldBorder(); ++ ++ @Nullable ++ IBlockAccess c(int i, int j); ++ ++ default boolean a(@Nullable Entity entity, VoxelShape voxelshape) { ++ return true; ++ } ++ ++ default boolean a(IBlockData iblockdata, BlockPosition blockposition, VoxelShapeCollision voxelshapecollision) { ++ VoxelShape voxelshape = iblockdata.b((IBlockAccess) this, blockposition, voxelshapecollision); ++ ++ return voxelshape.isEmpty() || this.a((Entity) null, voxelshape.a((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ())); ++ } ++ ++ default boolean j(Entity entity) { ++ return this.a(entity, VoxelShapes.a(entity.getBoundingBox())); ++ } ++ ++ default boolean b(AxisAlignedBB axisalignedbb) { ++ return this.b((Entity) null, axisalignedbb, (entity) -> { ++ return true; ++ }); ++ } ++ ++ default boolean getCubes(Entity entity) { ++ return this.b(entity, entity.getBoundingBox(), (entity1) -> { ++ return true; ++ }); ++ } ++ ++ default boolean getCubes(Entity entity, AxisAlignedBB axisalignedbb) { ++ return this.b(entity, axisalignedbb, (entity1) -> { ++ return true; ++ }); ++ } ++ ++ default boolean b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ return this.d(entity, axisalignedbb, predicate).allMatch(VoxelShape::isEmpty); ++ } ++ ++ Stream c(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate); ++ ++ default Stream d(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ return Stream.concat(this.b(entity, axisalignedbb), this.c(entity, axisalignedbb, predicate)); ++ } ++ ++ default Stream b(@Nullable Entity entity, AxisAlignedBB axisalignedbb) { ++ return StreamSupport.stream(new VoxelShapeSpliterator(this, entity, axisalignedbb), false); ++ } ++ ++ default Stream b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, BiPredicate bipredicate) { ++ return StreamSupport.stream(new VoxelShapeSpliterator(this, entity, axisalignedbb, bipredicate), false); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/IEntityAccess.java b/src/main/java/net/minecraft/world/level/IEntityAccess.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d24f97593777d6929271520f7501a800f1aadaa6 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/IEntityAccess.java +@@ -0,0 +1,243 @@ ++package net.minecraft.world.level; ++ ++import com.google.common.collect.Lists; ++import java.util.Iterator; ++import java.util.List; ++import java.util.UUID; ++import java.util.function.Predicate; ++import java.util.stream.Stream; ++import javax.annotation.Nullable; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.IEntitySelector; ++import net.minecraft.world.entity.ai.targeting.PathfinderTargetCondition; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.phys.AxisAlignedBB; ++import net.minecraft.world.phys.shapes.OperatorBoolean; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapes; ++ ++public interface IEntityAccess { ++ ++ List getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate); ++ ++ List a(Class oclass, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate); ++ ++ default List b(Class oclass, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { ++ return this.a(oclass, axisalignedbb, predicate); ++ } ++ ++ List getPlayers(); ++ ++ default List getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb) { ++ return this.getEntities(entity, axisalignedbb, IEntitySelector.g); ++ } ++ ++ default boolean a(@Nullable Entity entity, VoxelShape voxelshape) { ++ if (voxelshape.isEmpty()) { ++ return true; ++ } else { ++ Iterator iterator = this.getEntities(entity, voxelshape.getBoundingBox()).iterator(); ++ ++ Entity entity1; ++ ++ do { ++ if (!iterator.hasNext()) { ++ return true; ++ } ++ ++ entity1 = (Entity) iterator.next(); ++ } while (entity1.dead || !entity1.i || entity != null && entity1.isSameVehicle(entity) || !VoxelShapes.c(voxelshape, VoxelShapes.a(entity1.getBoundingBox()), OperatorBoolean.AND)); ++ ++ return false; ++ } ++ } ++ ++ default List a(Class oclass, AxisAlignedBB axisalignedbb) { ++ return this.a(oclass, axisalignedbb, IEntitySelector.g); ++ } ++ ++ default List b(Class oclass, AxisAlignedBB axisalignedbb) { ++ return this.b(oclass, axisalignedbb, IEntitySelector.g); ++ } ++ ++ default Stream c(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ if (axisalignedbb.a() < 1.0E-7D) { ++ return Stream.empty(); ++ } else { ++ AxisAlignedBB axisalignedbb1 = axisalignedbb.g(1.0E-7D); ++ ++ return this.getEntities(entity, axisalignedbb1, predicate.and((entity1) -> { ++ boolean flag; ++ ++ if (entity1.getBoundingBox().c(axisalignedbb1)) { ++ label25: ++ { ++ if (entity == null) { ++ if (!entity1.aZ()) { ++ break label25; ++ } ++ } else if (!entity.j(entity1)) { ++ break label25; ++ } ++ ++ flag = true; ++ return flag; ++ } ++ } ++ ++ flag = false; ++ return flag; ++ })).stream().map(Entity::getBoundingBox).map(VoxelShapes::a); ++ } ++ } ++ ++ @Nullable ++ default EntityHuman a(double d0, double d1, double d2, double d3, @Nullable Predicate predicate) { ++ double d4 = -1.0D; ++ EntityHuman entityhuman = null; ++ Iterator iterator = this.getPlayers().iterator(); ++ ++ while (iterator.hasNext()) { ++ EntityHuman entityhuman1 = (EntityHuman) iterator.next(); ++ ++ if (predicate == null || predicate.test(entityhuman1)) { ++ double d5 = entityhuman1.h(d0, d1, d2); ++ ++ if ((d3 < 0.0D || d5 < d3 * d3) && (d4 == -1.0D || d5 < d4)) { ++ d4 = d5; ++ entityhuman = entityhuman1; ++ } ++ } ++ } ++ ++ return entityhuman; ++ } ++ ++ @Nullable ++ default EntityHuman findNearbyPlayer(Entity entity, double d0) { ++ return this.a(entity.locX(), entity.locY(), entity.locZ(), d0, false); ++ } ++ ++ @Nullable ++ default EntityHuman a(double d0, double d1, double d2, double d3, boolean flag) { ++ Predicate predicate = flag ? IEntitySelector.e : IEntitySelector.g; ++ ++ return this.a(d0, d1, d2, d3, predicate); ++ } ++ ++ default boolean isPlayerNearby(double d0, double d1, double d2, double d3) { ++ Iterator iterator = this.getPlayers().iterator(); ++ ++ double d4; ++ ++ do { ++ EntityHuman entityhuman; ++ ++ do { ++ do { ++ if (!iterator.hasNext()) { ++ return false; ++ } ++ ++ entityhuman = (EntityHuman) iterator.next(); ++ } while (!IEntitySelector.g.test(entityhuman)); ++ } while (!IEntitySelector.b.test(entityhuman)); ++ ++ d4 = entityhuman.h(d0, d1, d2); ++ } while (d3 >= 0.0D && d4 >= d3 * d3); ++ ++ return true; ++ } ++ ++ @Nullable ++ default EntityHuman a(PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving) { ++ return (EntityHuman) this.a(this.getPlayers(), pathfindertargetcondition, entityliving, entityliving.locX(), entityliving.locY(), entityliving.locZ()); ++ } ++ ++ @Nullable ++ default EntityHuman a(PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving, double d0, double d1, double d2) { ++ return (EntityHuman) this.a(this.getPlayers(), pathfindertargetcondition, entityliving, d0, d1, d2); ++ } ++ ++ @Nullable ++ default EntityHuman a(PathfinderTargetCondition pathfindertargetcondition, double d0, double d1, double d2) { ++ return (EntityHuman) this.a(this.getPlayers(), pathfindertargetcondition, (EntityLiving) null, d0, d1, d2); ++ } ++ ++ @Nullable ++ default T a(Class oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { ++ return this.a(this.a(oclass, axisalignedbb, (Predicate) null), pathfindertargetcondition, entityliving, d0, d1, d2); ++ } ++ ++ @Nullable ++ default T b(Class oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { ++ return this.a(this.b(oclass, axisalignedbb, (Predicate) null), pathfindertargetcondition, entityliving, d0, d1, d2); ++ } ++ ++ @Nullable ++ default T a(List list, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2) { ++ double d3 = -1.0D; ++ T t0 = null; ++ Iterator iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ T t1 = (EntityLiving) iterator.next(); ++ ++ if (pathfindertargetcondition.a(entityliving, t1)) { ++ double d4 = t1.h(d0, d1, d2); ++ ++ if (d3 == -1.0D || d4 < d3) { ++ d3 = d4; ++ t0 = t1; ++ } ++ } ++ } ++ ++ return t0; ++ } ++ ++ default List a(PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving, AxisAlignedBB axisalignedbb) { ++ List list = Lists.newArrayList(); ++ Iterator iterator = this.getPlayers().iterator(); ++ ++ while (iterator.hasNext()) { ++ EntityHuman entityhuman = (EntityHuman) iterator.next(); ++ ++ if (axisalignedbb.e(entityhuman.locX(), entityhuman.locY(), entityhuman.locZ()) && pathfindertargetcondition.a(entityliving, entityhuman)) { ++ list.add(entityhuman); ++ } ++ } ++ ++ return list; ++ } ++ ++ default List a(Class oclass, PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving, AxisAlignedBB axisalignedbb) { ++ List list = this.a(oclass, axisalignedbb, (Predicate) null); ++ List list1 = Lists.newArrayList(); ++ Iterator iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ T t0 = (EntityLiving) iterator.next(); ++ ++ if (pathfindertargetcondition.a(entityliving, t0)) { ++ list1.add(t0); ++ } ++ } ++ ++ return list1; ++ } ++ ++ @Nullable ++ default EntityHuman b(UUID uuid) { ++ for (int i = 0; i < this.getPlayers().size(); ++i) { ++ EntityHuman entityhuman = (EntityHuman) this.getPlayers().get(i); ++ ++ if (uuid.equals(entityhuman.getUniqueID())) { ++ return entityhuman; ++ } ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/IWorldReader.java b/src/main/java/net/minecraft/world/level/IWorldReader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d3d33e77ce09d485552076c5ab6faf08a16d90db +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/IWorldReader.java +@@ -0,0 +1,188 @@ ++package net.minecraft.world.level; ++ ++import java.util.stream.Stream; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsFluid; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.level.biome.BiomeBase; ++import net.minecraft.world.level.biome.BiomeManager; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.dimension.DimensionManager; ++import net.minecraft.world.level.levelgen.HeightMap; ++import net.minecraft.world.phys.AxisAlignedBB; ++ ++public interface IWorldReader extends IBlockLightAccess, ICollisionAccess, BiomeManager.Provider { ++ ++ @Nullable ++ IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag); ++ ++ @Deprecated ++ boolean isChunkLoaded(int i, int j); ++ ++ int a(HeightMap.Type heightmap_type, int i, int j); ++ ++ int c(); ++ ++ BiomeManager d(); ++ ++ default BiomeBase getBiome(BlockPosition blockposition) { ++ return this.d().a(blockposition); ++ } ++ ++ default Stream c(AxisAlignedBB axisalignedbb) { ++ int i = MathHelper.floor(axisalignedbb.minX); ++ int j = MathHelper.floor(axisalignedbb.maxX); ++ int k = MathHelper.floor(axisalignedbb.minY); ++ int l = MathHelper.floor(axisalignedbb.maxY); ++ int i1 = MathHelper.floor(axisalignedbb.minZ); ++ int j1 = MathHelper.floor(axisalignedbb.maxZ); ++ ++ return this.isAreaLoaded(i, k, i1, j, l, j1) ? this.a(axisalignedbb) : Stream.empty(); ++ } ++ ++ @Override ++ default BiomeBase getBiome(int i, int j, int k) { ++ IChunkAccess ichunkaccess = this.getChunkAt(i >> 2, k >> 2, ChunkStatus.BIOMES, false); ++ ++ return ichunkaccess != null && ichunkaccess.getBiomeIndex() != null ? ichunkaccess.getBiomeIndex().getBiome(i, j, k) : this.a(i, j, k); ++ } ++ ++ BiomeBase a(int i, int j, int k); ++ ++ boolean s_(); ++ ++ @Deprecated ++ int getSeaLevel(); ++ ++ DimensionManager getDimensionManager(); ++ ++ default BlockPosition getHighestBlockYAt(HeightMap.Type heightmap_type, BlockPosition blockposition) { ++ return new BlockPosition(blockposition.getX(), this.a(heightmap_type, blockposition.getX(), blockposition.getZ()), blockposition.getZ()); ++ } ++ ++ default boolean isEmpty(BlockPosition blockposition) { ++ return this.getType(blockposition).isAir(); ++ } ++ ++ default boolean x(BlockPosition blockposition) { ++ if (blockposition.getY() >= this.getSeaLevel()) { ++ return this.e(blockposition); ++ } else { ++ BlockPosition blockposition1 = new BlockPosition(blockposition.getX(), this.getSeaLevel(), blockposition.getZ()); ++ ++ if (!this.e(blockposition1)) { ++ return false; ++ } else { ++ for (blockposition1 = blockposition1.down(); blockposition1.getY() > blockposition.getY(); blockposition1 = blockposition1.down()) { ++ IBlockData iblockdata = this.getType(blockposition1); ++ ++ if (iblockdata.b((IBlockAccess) this, blockposition1) > 0 && !iblockdata.getMaterial().isLiquid()) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ } ++ } ++ ++ @Deprecated ++ default float y(BlockPosition blockposition) { ++ return this.getDimensionManager().a(this.getLightLevel(blockposition)); ++ } ++ ++ default int c(BlockPosition blockposition, EnumDirection enumdirection) { ++ return this.getType(blockposition).c(this, blockposition, enumdirection); ++ } ++ ++ default IChunkAccess z(BlockPosition blockposition) { ++ return this.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ } ++ ++ default IChunkAccess getChunkAt(int i, int j) { ++ return this.getChunkAt(i, j, ChunkStatus.FULL, true); ++ } ++ ++ default IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus) { ++ return this.getChunkAt(i, j, chunkstatus, true); ++ } ++ ++ @Nullable ++ @Override ++ default IBlockAccess c(int i, int j) { ++ return this.getChunkAt(i, j, ChunkStatus.EMPTY, false); ++ } ++ ++ default boolean A(BlockPosition blockposition) { ++ return this.getFluid(blockposition).a((Tag) TagsFluid.WATER); ++ } ++ ++ default boolean containsLiquid(AxisAlignedBB axisalignedbb) { ++ int i = MathHelper.floor(axisalignedbb.minX); ++ int j = MathHelper.f(axisalignedbb.maxX); ++ int k = MathHelper.floor(axisalignedbb.minY); ++ int l = MathHelper.f(axisalignedbb.maxY); ++ int i1 = MathHelper.floor(axisalignedbb.minZ); ++ int j1 = MathHelper.f(axisalignedbb.maxZ); ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ ++ for (int k1 = i; k1 < j; ++k1) { ++ for (int l1 = k; l1 < l; ++l1) { ++ for (int i2 = i1; i2 < j1; ++i2) { ++ IBlockData iblockdata = this.getType(blockposition_mutableblockposition.d(k1, l1, i2)); ++ ++ if (!iblockdata.getFluid().isEmpty()) { ++ return true; ++ } ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ default int getLightLevel(BlockPosition blockposition) { ++ return this.c(blockposition, this.c()); ++ } ++ ++ default int c(BlockPosition blockposition, int i) { ++ return blockposition.getX() >= -30000000 && blockposition.getZ() >= -30000000 && blockposition.getX() < 30000000 && blockposition.getZ() < 30000000 ? this.getLightLevel(blockposition, i) : 15; ++ } ++ ++ @Deprecated ++ default boolean isLoaded(BlockPosition blockposition) { ++ return this.isChunkLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ } ++ ++ @Deprecated ++ default boolean areChunksLoadedBetween(BlockPosition blockposition, BlockPosition blockposition1) { ++ return this.isAreaLoaded(blockposition.getX(), blockposition.getY(), blockposition.getZ(), blockposition1.getX(), blockposition1.getY(), blockposition1.getZ()); ++ } ++ ++ @Deprecated ++ default boolean isAreaLoaded(int i, int j, int k, int l, int i1, int j1) { ++ if (i1 >= 0 && j < 256) { ++ i >>= 4; ++ k >>= 4; ++ l >>= 4; ++ j1 >>= 4; ++ ++ for (int k1 = i; k1 <= l; ++k1) { ++ for (int l1 = k; l1 <= j1; ++l1) { ++ if (!this.isChunkLoaded(k1, l1)) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } else { ++ return false; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/NextTickListEntry.java b/src/main/java/net/minecraft/world/level/NextTickListEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..116a5e4ded3ccf935fd143f2512098c22ec2ad76 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/NextTickListEntry.java +@@ -0,0 +1,58 @@ ++package net.minecraft.world.level; ++ ++import java.util.Comparator; ++import net.minecraft.core.BlockPosition; ++ ++public class NextTickListEntry { ++ ++ private static long d; ++ private final T e; ++ public final BlockPosition a; ++ public final long b; ++ public final TickListPriority c; ++ private final long f; ++ ++ public NextTickListEntry(BlockPosition blockposition, T t0) { ++ this(blockposition, t0, 0L, TickListPriority.NORMAL); ++ } ++ ++ public NextTickListEntry(BlockPosition blockposition, T t0, long i, TickListPriority ticklistpriority) { ++ this.f = (long) (NextTickListEntry.d++); ++ this.a = blockposition.immutableCopy(); ++ this.e = t0; ++ this.b = i; ++ this.c = ticklistpriority; ++ } ++ ++ public boolean equals(Object object) { ++ if (!(object instanceof NextTickListEntry)) { ++ return false; ++ } else { ++ NextTickListEntry nextticklistentry = (NextTickListEntry) object; ++ ++ return this.a.equals(nextticklistentry.a) && this.e == nextticklistentry.e; ++ } ++ } ++ ++ public int hashCode() { ++ return this.a.hashCode(); ++ } ++ ++ public static Comparator> a() { ++ return Comparator.comparingLong((nextticklistentry) -> { ++ return nextticklistentry.b; ++ }).thenComparing((nextticklistentry) -> { ++ return nextticklistentry.c; ++ }).thenComparingLong((nextticklistentry) -> { ++ return nextticklistentry.f; ++ }); ++ } ++ ++ public String toString() { ++ return this.e + ": " + this.a + ", " + this.b + ", " + this.c + ", " + this.f; ++ } ++ ++ public T b() { ++ return this.e; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/TickListChunk.java b/src/main/java/net/minecraft/world/level/TickListChunk.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c3cb513d0d107ecb43e98960b25054626aa6a03f +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/TickListChunk.java +@@ -0,0 +1,105 @@ ++package net.minecraft.world.level; ++ ++import com.google.common.collect.Lists; ++import java.util.Iterator; ++import java.util.List; ++import java.util.function.Function; ++import java.util.stream.Collectors; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.resources.MinecraftKey; ++ ++public class TickListChunk implements TickList { ++ ++ private final List> a; ++ private final Function b; ++ ++ public TickListChunk(Function function, List> list, long i) { ++ this(function, (List) list.stream().map((nextticklistentry) -> { ++ return new TickListChunk.a<>(nextticklistentry.b(), nextticklistentry.a, (int) (nextticklistentry.b - i), nextticklistentry.c); ++ }).collect(Collectors.toList())); ++ } ++ ++ private TickListChunk(Function function, List> list) { ++ this.a = list; ++ this.b = function; ++ } ++ ++ @Override ++ public boolean a(BlockPosition blockposition, T t0) { ++ return false; ++ } ++ ++ @Override ++ public void a(BlockPosition blockposition, T t0, int i, TickListPriority ticklistpriority) { ++ this.a.add(new TickListChunk.a<>(t0, blockposition, i, ticklistpriority)); ++ } ++ ++ @Override ++ public boolean b(BlockPosition blockposition, T t0) { ++ return false; ++ } ++ ++ public NBTTagList b() { ++ NBTTagList nbttaglist = new NBTTagList(); ++ Iterator iterator = this.a.iterator(); ++ ++ while (iterator.hasNext()) { ++ TickListChunk.a ticklistchunk_a = (TickListChunk.a) iterator.next(); ++ NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ ++ nbttagcompound.setString("i", ((MinecraftKey) this.b.apply(ticklistchunk_a.d)).toString()); ++ nbttagcompound.setInt("x", ticklistchunk_a.a.getX()); ++ nbttagcompound.setInt("y", ticklistchunk_a.a.getY()); ++ nbttagcompound.setInt("z", ticklistchunk_a.a.getZ()); ++ nbttagcompound.setInt("t", ticklistchunk_a.b); ++ nbttagcompound.setInt("p", ticklistchunk_a.c.a()); ++ nbttaglist.add(nbttagcompound); ++ } ++ ++ return nbttaglist; ++ } ++ ++ public static TickListChunk a(NBTTagList nbttaglist, Function function, Function function1) { ++ List> list = Lists.newArrayList(); ++ ++ for (int i = 0; i < nbttaglist.size(); ++i) { ++ NBTTagCompound nbttagcompound = nbttaglist.getCompound(i); ++ T t0 = function1.apply(new MinecraftKey(nbttagcompound.getString("i"))); ++ ++ if (t0 != null) { ++ BlockPosition blockposition = new BlockPosition(nbttagcompound.getInt("x"), nbttagcompound.getInt("y"), nbttagcompound.getInt("z")); ++ ++ list.add(new TickListChunk.a<>(t0, blockposition, nbttagcompound.getInt("t"), TickListPriority.a(nbttagcompound.getInt("p")))); ++ } ++ } ++ ++ return new TickListChunk<>(function, list); ++ } ++ ++ public void a(TickList ticklist) { ++ this.a.forEach((ticklistchunk_a) -> { ++ ticklist.a(ticklistchunk_a.a, ticklistchunk_a.d, ticklistchunk_a.b, ticklistchunk_a.c); ++ }); ++ } ++ ++ static class a { ++ ++ private final T d; ++ public final BlockPosition a; ++ public final int b; ++ public final TickListPriority c; ++ ++ private a(T t0, BlockPosition blockposition, int i, TickListPriority ticklistpriority) { ++ this.d = t0; ++ this.a = blockposition; ++ this.b = i; ++ this.c = ticklistpriority; ++ } ++ ++ public String toString() { ++ return this.d + ": " + this.a + ", " + this.b + ", " + this.c; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/VoxelShapeSpliterator.java b/src/main/java/net/minecraft/world/level/VoxelShapeSpliterator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fa3421c9cd8531618827627e9c524a8df77c4c58 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/VoxelShapeSpliterator.java +@@ -0,0 +1,156 @@ ++package net.minecraft.world.level; ++ ++import java.util.Objects; ++import java.util.Spliterators.AbstractSpliterator; ++import java.util.function.BiPredicate; ++import java.util.function.Consumer; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.CursorPosition; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.border.WorldBorder; ++import net.minecraft.world.phys.AxisAlignedBB; ++import net.minecraft.world.phys.shapes.OperatorBoolean; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapeCollision; ++import net.minecraft.world.phys.shapes.VoxelShapes; ++ ++public class VoxelShapeSpliterator extends AbstractSpliterator { ++ ++ @Nullable ++ private final Entity a; ++ private final AxisAlignedBB b; ++ private final VoxelShapeCollision c; ++ private final CursorPosition d; ++ private final BlockPosition.MutableBlockPosition e; ++ private final VoxelShape f; ++ private final ICollisionAccess g; ++ private boolean h; ++ private final BiPredicate i; ++ ++ public VoxelShapeSpliterator(ICollisionAccess icollisionaccess, @Nullable Entity entity, AxisAlignedBB axisalignedbb) { ++ this(icollisionaccess, entity, axisalignedbb, (iblockdata, blockposition) -> { ++ return true; ++ }); ++ } ++ ++ public VoxelShapeSpliterator(ICollisionAccess icollisionaccess, @Nullable Entity entity, AxisAlignedBB axisalignedbb, BiPredicate bipredicate) { ++ super(Long.MAX_VALUE, 1280); ++ this.c = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); ++ this.e = new BlockPosition.MutableBlockPosition(); ++ this.f = VoxelShapes.a(axisalignedbb); ++ this.g = icollisionaccess; ++ this.h = entity != null; ++ this.a = entity; ++ this.b = axisalignedbb; ++ this.i = bipredicate; ++ int i = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 1; ++ int j = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 1; ++ int k = MathHelper.floor(axisalignedbb.minY - 1.0E-7D) - 1; ++ int l = MathHelper.floor(axisalignedbb.maxY + 1.0E-7D) + 1; ++ int i1 = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 1; ++ int j1 = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 1; ++ ++ this.d = new CursorPosition(i, k, i1, j, l, j1); ++ } ++ ++ public boolean tryAdvance(Consumer consumer) { ++ return this.h && this.b(consumer) || this.a(consumer); ++ } ++ ++ boolean a(Consumer consumer) { ++ while (true) { ++ if (this.d.a()) { ++ int i = this.d.b(); ++ int j = this.d.c(); ++ int k = this.d.d(); ++ int l = this.d.e(); ++ ++ if (l == 3) { ++ continue; ++ } ++ ++ IBlockAccess iblockaccess = this.a(i, k); ++ ++ if (iblockaccess == null) { ++ continue; ++ } ++ ++ this.e.d(i, j, k); ++ IBlockData iblockdata = iblockaccess.getType(this.e); ++ ++ if (!this.i.test(iblockdata, this.e) || l == 1 && !iblockdata.d() || l == 2 && !iblockdata.a(Blocks.MOVING_PISTON)) { ++ continue; ++ } ++ ++ VoxelShape voxelshape = iblockdata.b((IBlockAccess) this.g, this.e, this.c); ++ ++ if (voxelshape == VoxelShapes.b()) { ++ if (!this.b.a((double) i, (double) j, (double) k, (double) i + 1.0D, (double) j + 1.0D, (double) k + 1.0D)) { ++ continue; ++ } ++ ++ consumer.accept(voxelshape.a((double) i, (double) j, (double) k)); ++ return true; ++ } ++ ++ VoxelShape voxelshape1 = voxelshape.a((double) i, (double) j, (double) k); ++ ++ if (!VoxelShapes.c(voxelshape1, this.f, OperatorBoolean.AND)) { ++ continue; ++ } ++ ++ consumer.accept(voxelshape1); ++ return true; ++ } ++ ++ return false; ++ } ++ } ++ ++ @Nullable ++ private IBlockAccess a(int i, int j) { ++ int k = i >> 4; ++ int l = j >> 4; ++ ++ return this.g.c(k, l); ++ } ++ ++ boolean b(Consumer consumer) { ++ Objects.requireNonNull(this.a); ++ this.h = false; ++ WorldBorder worldborder = this.g.getWorldBorder(); ++ AxisAlignedBB axisalignedbb = this.a.getBoundingBox(); ++ ++ if (!a(worldborder, axisalignedbb)) { ++ VoxelShape voxelshape = worldborder.c(); ++ ++ if (!b(voxelshape, axisalignedbb) && a(voxelshape, axisalignedbb)) { ++ consumer.accept(voxelshape); ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ private static boolean a(VoxelShape voxelshape, AxisAlignedBB axisalignedbb) { ++ return VoxelShapes.c(voxelshape, VoxelShapes.a(axisalignedbb.g(1.0E-7D)), OperatorBoolean.AND); ++ } ++ ++ private static boolean b(VoxelShape voxelshape, AxisAlignedBB axisalignedbb) { ++ return VoxelShapes.c(voxelshape, VoxelShapes.a(axisalignedbb.shrink(1.0E-7D)), OperatorBoolean.AND); ++ } ++ ++ public static boolean a(WorldBorder worldborder, AxisAlignedBB axisalignedbb) { ++ double d0 = (double) MathHelper.floor(worldborder.e()); ++ double d1 = (double) MathHelper.floor(worldborder.f()); ++ double d2 = (double) MathHelper.f(worldborder.g()); ++ double d3 = (double) MathHelper.f(worldborder.h()); ++ ++ return axisalignedbb.minX > d0 && axisalignedbb.minX < d2 && axisalignedbb.minZ > d1 && axisalignedbb.minZ < d3 && axisalignedbb.maxX > d0 && axisalignedbb.maxX < d2 && axisalignedbb.maxZ > d1 && axisalignedbb.maxZ < d3; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeBase.java b/src/main/java/net/minecraft/world/level/biome/BiomeBase.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6672d74426d6a334d52f641c48d3a352c2bb6605 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/biome/BiomeBase.java +@@ -0,0 +1,586 @@ ++package net.minecraft.world.level.biome; ++ ++import com.google.common.collect.ImmutableList; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.MapCodec; ++import com.mojang.serialization.codecs.RecordCodecBuilder; ++import it.unimi.dsi.fastutil.longs.Long2FloatLinkedOpenHashMap; ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Random; ++import java.util.function.Supplier; ++import java.util.stream.Collectors; ++import javax.annotation.Nullable; ++import net.minecraft.CrashReport; ++import net.minecraft.ReportedException; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.IRegistry; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.data.RegistryGeneration; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.resources.RegistryFileCodec; ++import net.minecraft.server.level.RegionLimitedWorldAccess; ++import net.minecraft.util.INamable; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.IWorldReader; ++import net.minecraft.world.level.StructureManager; ++import net.minecraft.world.level.block.BlockFluids; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.ChunkGenerator; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.levelgen.SeededRandom; ++import net.minecraft.world.level.levelgen.WorldGenStage; ++import net.minecraft.world.level.levelgen.feature.StructureGenerator; ++import net.minecraft.world.level.levelgen.feature.WorldGenFeatureConfigured; ++import net.minecraft.world.level.levelgen.structure.StructureBoundingBox; ++import net.minecraft.world.level.levelgen.surfacebuilders.WorldGenSurfaceComposite; ++import net.minecraft.world.level.levelgen.synth.NoiseGenerator3; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.level.material.FluidTypes; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public final class BiomeBase { ++ ++ public static final Logger LOGGER = LogManager.getLogger(); ++ public static final Codec b = RecordCodecBuilder.create((instance) -> { ++ return instance.group(BiomeBase.d.a.forGetter((biomebase) -> { ++ return biomebase.j; ++ }), BiomeBase.Geography.r.fieldOf("category").forGetter((biomebase) -> { ++ return biomebase.o; ++ }), Codec.FLOAT.fieldOf("depth").forGetter((biomebase) -> { ++ return biomebase.m; ++ }), Codec.FLOAT.fieldOf("scale").forGetter((biomebase) -> { ++ return biomebase.n; ++ }), BiomeFog.a.fieldOf("effects").forGetter((biomebase) -> { ++ return biomebase.p; ++ }), BiomeSettingsGeneration.c.forGetter((biomebase) -> { ++ return biomebase.k; ++ }), BiomeSettingsMobs.c.forGetter((biomebase) -> { ++ return biomebase.l; ++ })).apply(instance, BiomeBase::new); ++ }); ++ public static final Codec c = RecordCodecBuilder.create((instance) -> { ++ return instance.group(BiomeBase.d.a.forGetter((biomebase) -> { ++ return biomebase.j; ++ }), BiomeBase.Geography.r.fieldOf("category").forGetter((biomebase) -> { ++ return biomebase.o; ++ }), Codec.FLOAT.fieldOf("depth").forGetter((biomebase) -> { ++ return biomebase.m; ++ }), Codec.FLOAT.fieldOf("scale").forGetter((biomebase) -> { ++ return biomebase.n; ++ }), BiomeFog.a.fieldOf("effects").forGetter((biomebase) -> { ++ return biomebase.p; ++ })).apply(instance, (biomebase_d, biomebase_geography, ofloat, ofloat1, biomefog) -> { ++ return new BiomeBase(biomebase_d, biomebase_geography, ofloat, ofloat1, biomefog, BiomeSettingsGeneration.b, BiomeSettingsMobs.b); ++ }); ++ }); ++ public static final Codec> d = RegistryFileCodec.a(IRegistry.ay, BiomeBase.b); ++ public static final Codec>> e = RegistryFileCodec.b(IRegistry.ay, BiomeBase.b); ++ private final Map>> g; ++ private static final NoiseGenerator3 h = new NoiseGenerator3(new SeededRandom(1234L), ImmutableList.of(0)); ++ private static final NoiseGenerator3 i = new NoiseGenerator3(new SeededRandom(3456L), ImmutableList.of(-2, -1, 0)); ++ public static final NoiseGenerator3 f = new NoiseGenerator3(new SeededRandom(2345L), ImmutableList.of(0)); ++ private final BiomeBase.d j; ++ private final BiomeSettingsGeneration k; ++ private final BiomeSettingsMobs l; ++ private final float m; ++ private final float n; ++ private final BiomeBase.Geography o; ++ private final BiomeFog p; ++ private final ThreadLocal q; ++ ++ private BiomeBase(BiomeBase.d biomebase_d, BiomeBase.Geography biomebase_geography, float f, float f1, BiomeFog biomefog, BiomeSettingsGeneration biomesettingsgeneration, BiomeSettingsMobs biomesettingsmobs) { ++ this.g = (Map) IRegistry.STRUCTURE_FEATURE.g().collect(Collectors.groupingBy((structuregenerator) -> { ++ return structuregenerator.f().ordinal(); ++ })); ++ this.q = ThreadLocal.withInitial(() -> { ++ return (Long2FloatLinkedOpenHashMap) SystemUtils.a(() -> { ++ Long2FloatLinkedOpenHashMap long2floatlinkedopenhashmap = new Long2FloatLinkedOpenHashMap(1024, 0.25F) { ++ protected void rehash(int i) {} ++ }; ++ ++ long2floatlinkedopenhashmap.defaultReturnValue(Float.NaN); ++ return long2floatlinkedopenhashmap; ++ }); ++ }); ++ this.j = biomebase_d; ++ this.k = biomesettingsgeneration; ++ this.l = biomesettingsmobs; ++ this.o = biomebase_geography; ++ this.m = f; ++ this.n = f1; ++ this.p = biomefog; ++ } ++ ++ public BiomeSettingsMobs b() { ++ return this.l; ++ } ++ ++ public BiomeBase.Precipitation c() { ++ return this.j.b; ++ } ++ ++ public boolean d() { ++ return this.getHumidity() > 0.85F; ++ } ++ ++ private float b(BlockPosition blockposition) { ++ float f = this.j.d.a(blockposition, this.k()); ++ ++ if (blockposition.getY() > 64) { ++ float f1 = (float) (BiomeBase.h.a((double) ((float) blockposition.getX() / 8.0F), (double) ((float) blockposition.getZ() / 8.0F), false) * 4.0D); ++ ++ return f - (f1 + (float) blockposition.getY() - 64.0F) * 0.05F / 30.0F; ++ } else { ++ return f; ++ } ++ } ++ ++ public final float getAdjustedTemperature(BlockPosition blockposition) { ++ long i = blockposition.asLong(); ++ Long2FloatLinkedOpenHashMap long2floatlinkedopenhashmap = (Long2FloatLinkedOpenHashMap) this.q.get(); ++ float f = long2floatlinkedopenhashmap.get(i); ++ ++ if (!Float.isNaN(f)) { ++ return f; ++ } else { ++ float f1 = this.b(blockposition); ++ ++ if (long2floatlinkedopenhashmap.size() == 1024) { ++ long2floatlinkedopenhashmap.removeFirstFloat(); ++ } ++ ++ long2floatlinkedopenhashmap.put(i, f1); ++ return f1; ++ } ++ } ++ ++ public boolean a(IWorldReader iworldreader, BlockPosition blockposition) { ++ return this.a(iworldreader, blockposition, true); ++ } ++ ++ public boolean a(IWorldReader iworldreader, BlockPosition blockposition, boolean flag) { ++ if (this.getAdjustedTemperature(blockposition) >= 0.15F) { ++ return false; ++ } else { ++ if (blockposition.getY() >= 0 && blockposition.getY() < 256 && iworldreader.getBrightness(EnumSkyBlock.BLOCK, blockposition) < 10) { ++ IBlockData iblockdata = iworldreader.getType(blockposition); ++ Fluid fluid = iworldreader.getFluid(blockposition); ++ ++ if (fluid.getType() == FluidTypes.WATER && iblockdata.getBlock() instanceof BlockFluids) { ++ if (!flag) { ++ return true; ++ } ++ ++ boolean flag1 = iworldreader.A(blockposition.west()) && iworldreader.A(blockposition.east()) && iworldreader.A(blockposition.north()) && iworldreader.A(blockposition.south()); ++ ++ if (!flag1) { ++ return true; ++ } ++ } ++ } ++ ++ return false; ++ } ++ } ++ ++ public boolean b(IWorldReader iworldreader, BlockPosition blockposition) { ++ if (this.getAdjustedTemperature(blockposition) >= 0.15F) { ++ return false; ++ } else { ++ if (blockposition.getY() >= 0 && blockposition.getY() < 256 && iworldreader.getBrightness(EnumSkyBlock.BLOCK, blockposition) < 10) { ++ IBlockData iblockdata = iworldreader.getType(blockposition); ++ ++ if (iblockdata.isAir() && Blocks.SNOW.getBlockData().canPlace(iworldreader, blockposition)) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ } ++ ++ public BiomeSettingsGeneration e() { ++ return this.k; ++ } ++ ++ public void a(StructureManager structuremanager, ChunkGenerator chunkgenerator, RegionLimitedWorldAccess regionlimitedworldaccess, long i, SeededRandom seededrandom, BlockPosition blockposition) { ++ List>>> list = this.k.c(); ++ int j = WorldGenStage.Decoration.values().length; ++ ++ for (int k = 0; k < j; ++k) { ++ int l = 0; ++ ++ if (structuremanager.a()) { ++ List> list1 = (List) this.g.getOrDefault(k, Collections.emptyList()); ++ ++ for (Iterator iterator = list1.iterator(); iterator.hasNext(); ++l) { ++ StructureGenerator structuregenerator = (StructureGenerator) iterator.next(); ++ ++ seededrandom.b(i, l, k); ++ int i1 = blockposition.getX() >> 4; ++ int j1 = blockposition.getZ() >> 4; ++ int k1 = i1 << 4; ++ int l1 = j1 << 4; ++ ++ try { ++ structuremanager.a(SectionPosition.a(blockposition), structuregenerator).forEach((structurestart) -> { ++ structurestart.a(regionlimitedworldaccess, structuremanager, chunkgenerator, seededrandom, new StructureBoundingBox(k1, l1, k1 + 15, l1 + 15), new ChunkCoordIntPair(i1, j1)); ++ }); ++ } catch (Exception exception) { ++ CrashReport crashreport = CrashReport.a(exception, "Feature placement"); ++ ++ crashreport.a("Feature").a("Id", (Object) IRegistry.STRUCTURE_FEATURE.getKey(structuregenerator)).a("Description", () -> { ++ return structuregenerator.toString(); ++ }); ++ throw new ReportedException(crashreport); ++ } ++ } ++ } ++ ++ if (list.size() > k) { ++ for (Iterator iterator1 = ((List) list.get(k)).iterator(); iterator1.hasNext(); ++l) { ++ Supplier> supplier = (Supplier) iterator1.next(); ++ WorldGenFeatureConfigured worldgenfeatureconfigured = (WorldGenFeatureConfigured) supplier.get(); ++ ++ seededrandom.b(i, l, k); ++ ++ try { ++ worldgenfeatureconfigured.a(regionlimitedworldaccess, chunkgenerator, seededrandom, blockposition); ++ } catch (Exception exception1) { ++ CrashReport crashreport1 = CrashReport.a(exception1, "Feature placement"); ++ ++ crashreport1.a("Feature").a("Id", (Object) IRegistry.FEATURE.getKey(worldgenfeatureconfigured.e)).a("Config", (Object) worldgenfeatureconfigured.f).a("Description", () -> { ++ return worldgenfeatureconfigured.e.toString(); ++ }); ++ throw new ReportedException(crashreport1); ++ } ++ } ++ } ++ } ++ ++ } ++ ++ public void a(Random random, IChunkAccess ichunkaccess, int i, int j, int k, double d0, IBlockData iblockdata, IBlockData iblockdata1, int l, long i1) { ++ WorldGenSurfaceComposite worldgensurfacecomposite = (WorldGenSurfaceComposite) this.k.d().get(); ++ ++ worldgensurfacecomposite.a(i1); ++ worldgensurfacecomposite.a(random, ichunkaccess, this, i, j, k, d0, iblockdata, iblockdata1, l, i1); ++ } ++ ++ public final float h() { ++ return this.m; ++ } ++ ++ public final float getHumidity() { ++ return this.j.e; ++ } ++ ++ public final float j() { ++ return this.n; ++ } ++ ++ public final float k() { ++ return this.j.c; ++ } ++ ++ public BiomeFog l() { ++ return this.p; ++ } ++ ++ public final BiomeBase.Geography t() { ++ return this.o; ++ } ++ ++ public String toString() { ++ MinecraftKey minecraftkey = RegistryGeneration.WORLDGEN_BIOME.getKey(this); ++ ++ return minecraftkey == null ? super.toString() : minecraftkey.toString(); ++ } ++ ++ static class d { ++ ++ public static final MapCodec a = RecordCodecBuilder.mapCodec((instance) -> { ++ return instance.group(BiomeBase.Precipitation.d.fieldOf("precipitation").forGetter((biomebase_d) -> { ++ return biomebase_d.b; ++ }), Codec.FLOAT.fieldOf("temperature").forGetter((biomebase_d) -> { ++ return biomebase_d.c; ++ }), BiomeBase.TemperatureModifier.c.optionalFieldOf("temperature_modifier", BiomeBase.TemperatureModifier.NONE).forGetter((biomebase_d) -> { ++ return biomebase_d.d; ++ }), Codec.FLOAT.fieldOf("downfall").forGetter((biomebase_d) -> { ++ return biomebase_d.e; ++ })).apply(instance, BiomeBase.d::new); ++ }); ++ private final BiomeBase.Precipitation b; ++ private final float c; ++ private final BiomeBase.TemperatureModifier d; ++ private final float e; ++ ++ private d(BiomeBase.Precipitation biomebase_precipitation, float f, BiomeBase.TemperatureModifier biomebase_temperaturemodifier, float f1) { ++ this.b = biomebase_precipitation; ++ this.c = f; ++ this.d = biomebase_temperaturemodifier; ++ this.e = f1; ++ } ++ } ++ ++ public static class c { ++ ++ public static final Codec a = RecordCodecBuilder.create((instance) -> { ++ return instance.group(Codec.floatRange(-2.0F, 2.0F).fieldOf("temperature").forGetter((biomebase_c) -> { ++ return biomebase_c.b; ++ }), Codec.floatRange(-2.0F, 2.0F).fieldOf("humidity").forGetter((biomebase_c) -> { ++ return biomebase_c.c; ++ }), Codec.floatRange(-2.0F, 2.0F).fieldOf("altitude").forGetter((biomebase_c) -> { ++ return biomebase_c.d; ++ }), Codec.floatRange(-2.0F, 2.0F).fieldOf("weirdness").forGetter((biomebase_c) -> { ++ return biomebase_c.e; ++ }), Codec.floatRange(0.0F, 1.0F).fieldOf("offset").forGetter((biomebase_c) -> { ++ return biomebase_c.f; ++ })).apply(instance, BiomeBase.c::new); ++ }); ++ private final float b; ++ private final float c; ++ private final float d; ++ private final float e; ++ private final float f; ++ ++ public c(float f, float f1, float f2, float f3, float f4) { ++ this.b = f; ++ this.c = f1; ++ this.d = f2; ++ this.e = f3; ++ this.f = f4; ++ } ++ ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } else if (object != null && this.getClass() == object.getClass()) { ++ BiomeBase.c biomebase_c = (BiomeBase.c) object; ++ ++ return Float.compare(biomebase_c.b, this.b) != 0 ? false : (Float.compare(biomebase_c.c, this.c) != 0 ? false : (Float.compare(biomebase_c.d, this.d) != 0 ? false : Float.compare(biomebase_c.e, this.e) == 0)); ++ } else { ++ return false; ++ } ++ } ++ ++ public int hashCode() { ++ int i = this.b != 0.0F ? Float.floatToIntBits(this.b) : 0; ++ ++ i = 31 * i + (this.c != 0.0F ? Float.floatToIntBits(this.c) : 0); ++ i = 31 * i + (this.d != 0.0F ? Float.floatToIntBits(this.d) : 0); ++ i = 31 * i + (this.e != 0.0F ? Float.floatToIntBits(this.e) : 0); ++ return i; ++ } ++ ++ public float a(BiomeBase.c biomebase_c) { ++ return (this.b - biomebase_c.b) * (this.b - biomebase_c.b) + (this.c - biomebase_c.c) * (this.c - biomebase_c.c) + (this.d - biomebase_c.d) * (this.d - biomebase_c.d) + (this.e - biomebase_c.e) * (this.e - biomebase_c.e) + (this.f - biomebase_c.f) * (this.f - biomebase_c.f); ++ } ++ } ++ ++ public static class a { ++ ++ @Nullable ++ private BiomeBase.Precipitation a; ++ @Nullable ++ private BiomeBase.Geography b; ++ @Nullable ++ private Float c; ++ @Nullable ++ private Float d; ++ @Nullable ++ private Float e; ++ private BiomeBase.TemperatureModifier f; ++ @Nullable ++ private Float g; ++ @Nullable ++ private BiomeFog h; ++ @Nullable ++ private BiomeSettingsMobs i; ++ @Nullable ++ private BiomeSettingsGeneration j; ++ ++ public a() { ++ this.f = BiomeBase.TemperatureModifier.NONE; ++ } ++ ++ public BiomeBase.a a(BiomeBase.Precipitation biomebase_precipitation) { ++ this.a = biomebase_precipitation; ++ return this; ++ } ++ ++ public BiomeBase.a a(BiomeBase.Geography biomebase_geography) { ++ this.b = biomebase_geography; ++ return this; ++ } ++ ++ public BiomeBase.a a(float f) { ++ this.c = f; ++ return this; ++ } ++ ++ public BiomeBase.a b(float f) { ++ this.d = f; ++ return this; ++ } ++ ++ public BiomeBase.a c(float f) { ++ this.e = f; ++ return this; ++ } ++ ++ public BiomeBase.a d(float f) { ++ this.g = f; ++ return this; ++ } ++ ++ public BiomeBase.a a(BiomeFog biomefog) { ++ this.h = biomefog; ++ return this; ++ } ++ ++ public BiomeBase.a a(BiomeSettingsMobs biomesettingsmobs) { ++ this.i = biomesettingsmobs; ++ return this; ++ } ++ ++ public BiomeBase.a a(BiomeSettingsGeneration biomesettingsgeneration) { ++ this.j = biomesettingsgeneration; ++ return this; ++ } ++ ++ public BiomeBase.a a(BiomeBase.TemperatureModifier biomebase_temperaturemodifier) { ++ this.f = biomebase_temperaturemodifier; ++ return this; ++ } ++ ++ public BiomeBase a() { ++ if (this.a != null && this.b != null && this.c != null && this.d != null && this.e != null && this.g != null && this.h != null && this.i != null && this.j != null) { ++ return new BiomeBase(new BiomeBase.d(this.a, this.e, this.f, this.g), this.b, this.c, this.d, this.h, this.j, this.i); ++ } else { ++ throw new IllegalStateException("You are missing parameters to build a proper biome\n" + this); ++ } ++ } ++ ++ public String toString() { ++ return "BiomeBuilder{\nprecipitation=" + this.a + ",\nbiomeCategory=" + this.b + ",\ndepth=" + this.c + ",\nscale=" + this.d + ",\ntemperature=" + this.e + ",\ntemperatureModifier=" + this.f + ",\ndownfall=" + this.g + ",\nspecialEffects=" + this.h + ",\nmobSpawnSettings=" + this.i + ",\ngenerationSettings=" + this.j + ",\n" + '}'; ++ } ++ } ++ ++ public static enum TemperatureModifier implements INamable { ++ ++ NONE("none") { ++ @Override ++ public float a(BlockPosition blockposition, float f) { ++ return f; ++ } ++ }, ++ FROZEN("frozen") { ++ @Override ++ public float a(BlockPosition blockposition, float f) { ++ double d0 = BiomeBase.i.a((double) blockposition.getX() * 0.05D, (double) blockposition.getZ() * 0.05D, false) * 7.0D; ++ double d1 = BiomeBase.f.a((double) blockposition.getX() * 0.2D, (double) blockposition.getZ() * 0.2D, false); ++ double d2 = d0 + d1; ++ ++ if (d2 < 0.3D) { ++ double d3 = BiomeBase.f.a((double) blockposition.getX() * 0.09D, (double) blockposition.getZ() * 0.09D, false); ++ ++ if (d3 < 0.8D) { ++ return 0.2F; ++ } ++ } ++ ++ return f; ++ } ++ }; ++ ++ private final String d; ++ public static final Codec c = INamable.a(BiomeBase.TemperatureModifier::values, BiomeBase.TemperatureModifier::a); ++ private static final Map e = (Map) Arrays.stream(values()).collect(Collectors.toMap(BiomeBase.TemperatureModifier::b, (biomebase_temperaturemodifier) -> { ++ return biomebase_temperaturemodifier; ++ })); ++ ++ public abstract float a(BlockPosition blockposition, float f); ++ ++ private TemperatureModifier(String s) { ++ this.d = s; ++ } ++ ++ public String b() { ++ return this.d; ++ } ++ ++ @Override ++ public String getName() { ++ return this.d; ++ } ++ ++ public static BiomeBase.TemperatureModifier a(String s) { ++ return (BiomeBase.TemperatureModifier) BiomeBase.TemperatureModifier.e.get(s); ++ } ++ } ++ ++ public static enum Precipitation implements INamable { ++ ++ NONE("none"), RAIN("rain"), SNOW("snow"); ++ ++ public static final Codec d = INamable.a(BiomeBase.Precipitation::values, BiomeBase.Precipitation::a); ++ private static final Map e = (Map) Arrays.stream(values()).collect(Collectors.toMap(BiomeBase.Precipitation::b, (biomebase_precipitation) -> { ++ return biomebase_precipitation; ++ })); ++ private final String f; ++ ++ private Precipitation(String s) { ++ this.f = s; ++ } ++ ++ public String b() { ++ return this.f; ++ } ++ ++ public static BiomeBase.Precipitation a(String s) { ++ return (BiomeBase.Precipitation) BiomeBase.Precipitation.e.get(s); ++ } ++ ++ @Override ++ public String getName() { ++ return this.f; ++ } ++ } ++ ++ public static enum Geography implements INamable { ++ ++ NONE("none"), TAIGA("taiga"), EXTREME_HILLS("extreme_hills"), JUNGLE("jungle"), MESA("mesa"), PLAINS("plains"), SAVANNA("savanna"), ICY("icy"), THEEND("the_end"), BEACH("beach"), FOREST("forest"), OCEAN("ocean"), DESERT("desert"), RIVER("river"), SWAMP("swamp"), MUSHROOM("mushroom"), NETHER("nether"); ++ ++ public static final Codec r = INamable.a(BiomeBase.Geography::values, BiomeBase.Geography::a); ++ private static final Map s = (Map) Arrays.stream(values()).collect(Collectors.toMap(BiomeBase.Geography::b, (biomebase_geography) -> { ++ return biomebase_geography; ++ })); ++ private final String t; ++ ++ private Geography(String s) { ++ this.t = s; ++ } ++ ++ public String b() { ++ return this.t; ++ } ++ ++ public static BiomeBase.Geography a(String s) { ++ return (BiomeBase.Geography) BiomeBase.Geography.s.get(s); ++ } ++ ++ @Override ++ public String getName() { ++ return this.t; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..340508e0ba8b8883a3037ecaa2d4e09e61e709d3 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java +@@ -0,0 +1,34 @@ ++package net.minecraft.world.level.biome; ++ ++import com.google.common.hash.Hashing; ++import net.minecraft.core.BlockPosition; ++ ++public class BiomeManager { ++ ++ private final BiomeManager.Provider a; ++ private final long b; ++ private final GenLayerZoomer c; ++ ++ public BiomeManager(BiomeManager.Provider biomemanager_provider, long i, GenLayerZoomer genlayerzoomer) { ++ this.a = biomemanager_provider; ++ this.b = i; ++ this.c = genlayerzoomer; ++ } ++ ++ public static long a(long i) { ++ return Hashing.sha256().hashLong(i).asLong(); ++ } ++ ++ public BiomeManager a(WorldChunkManager worldchunkmanager) { ++ return new BiomeManager(worldchunkmanager, this.b, this.c); ++ } ++ ++ public BiomeBase a(BlockPosition blockposition) { ++ return this.c.a(this.b, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this.a); ++ } ++ ++ public interface Provider { ++ ++ BiomeBase getBiome(int i, int j, int k); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeSettingsMobs.java b/src/main/java/net/minecraft/world/level/biome/BiomeSettingsMobs.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5adaf5fdaaec25220878213df2c0839ccf025d63 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/biome/BiomeSettingsMobs.java +@@ -0,0 +1,171 @@ ++package net.minecraft.world.level.biome; ++ ++import com.google.common.collect.ImmutableList; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.MapCodec; ++import com.mojang.serialization.codecs.RecordCodecBuilder; ++import java.util.Collection; ++import java.util.List; ++import java.util.Map; ++import java.util.Map.Entry; ++import java.util.stream.Stream; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.IRegistry; ++import net.minecraft.util.INamable; ++import net.minecraft.util.WeightedRandom; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.EnumCreatureType; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class BiomeSettingsMobs { ++ ++ public static final Logger LOGGER = LogManager.getLogger(); ++ public static final BiomeSettingsMobs b = new BiomeSettingsMobs(0.1F, (Map) Stream.of(EnumCreatureType.values()).collect(ImmutableMap.toImmutableMap((enumcreaturetype) -> { ++ return enumcreaturetype; ++ }, (enumcreaturetype) -> { ++ return ImmutableList.of(); ++ })), ImmutableMap.of(), false); ++ public static final MapCodec c = RecordCodecBuilder.mapCodec((instance) -> { ++ RecordCodecBuilder recordcodecbuilder = Codec.FLOAT.optionalFieldOf("creature_spawn_probability", 0.1F).forGetter((biomesettingsmobs) -> { ++ return biomesettingsmobs.d; ++ }); ++ Codec codec = EnumCreatureType.g; ++ Codec codec1 = BiomeSettingsMobs.c.b.listOf(); ++ Logger logger = BiomeSettingsMobs.LOGGER; ++ ++ logger.getClass(); ++ return instance.group(recordcodecbuilder, Codec.simpleMap(codec, codec1.promotePartial(SystemUtils.a("Spawn data: ", logger::error)), INamable.a(EnumCreatureType.values())).fieldOf("spawners").forGetter((biomesettingsmobs) -> { ++ return biomesettingsmobs.e; ++ }), Codec.simpleMap(IRegistry.ENTITY_TYPE, BiomeSettingsMobs.b.a, IRegistry.ENTITY_TYPE).fieldOf("spawn_costs").forGetter((biomesettingsmobs) -> { ++ return biomesettingsmobs.f; ++ }), Codec.BOOL.fieldOf("player_spawn_friendly").orElse(false).forGetter(BiomeSettingsMobs::b)).apply(instance, BiomeSettingsMobs::new); ++ }); ++ private final float d; ++ private final Map> e; ++ private final Map, BiomeSettingsMobs.b> f; ++ private final boolean g; ++ ++ private BiomeSettingsMobs(float f, Map> map, Map, BiomeSettingsMobs.b> map1, boolean flag) { ++ this.d = f; ++ this.e = map; ++ this.f = map1; ++ this.g = flag; ++ } ++ ++ public List a(EnumCreatureType enumcreaturetype) { ++ return (List) this.e.getOrDefault(enumcreaturetype, ImmutableList.of()); ++ } ++ ++ @Nullable ++ public BiomeSettingsMobs.b a(EntityTypes entitytypes) { ++ return (BiomeSettingsMobs.b) this.f.get(entitytypes); ++ } ++ ++ public float a() { ++ return this.d; ++ } ++ ++ public boolean b() { ++ return this.g; ++ } ++ ++ public static class a { ++ ++ private final Map> a = (Map) Stream.of(EnumCreatureType.values()).collect(ImmutableMap.toImmutableMap((enumcreaturetype) -> { ++ return enumcreaturetype; ++ }, (enumcreaturetype) -> { ++ return Lists.newArrayList(); ++ })); ++ private final Map, BiomeSettingsMobs.b> b = Maps.newLinkedHashMap(); ++ private float c = 0.1F; ++ private boolean d; ++ ++ public a() {} ++ ++ public BiomeSettingsMobs.a a(EnumCreatureType enumcreaturetype, BiomeSettingsMobs.c biomesettingsmobs_c) { ++ ((List) this.a.get(enumcreaturetype)).add(biomesettingsmobs_c); ++ return this; ++ } ++ ++ public BiomeSettingsMobs.a a(EntityTypes entitytypes, double d0, double d1) { ++ this.b.put(entitytypes, new BiomeSettingsMobs.b(d1, d0)); ++ return this; ++ } ++ ++ public BiomeSettingsMobs.a a(float f) { ++ this.c = f; ++ return this; ++ } ++ ++ public BiomeSettingsMobs.a a() { ++ this.d = true; ++ return this; ++ } ++ ++ public BiomeSettingsMobs b() { ++ return new BiomeSettingsMobs(this.c, (Map) this.a.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> { ++ return ImmutableList.copyOf((Collection) entry.getValue()); ++ })), ImmutableMap.copyOf(this.b), this.d); ++ } ++ } ++ ++ public static class b { ++ ++ public static final Codec a = RecordCodecBuilder.create((instance) -> { ++ return instance.group(Codec.DOUBLE.fieldOf("energy_budget").forGetter((biomesettingsmobs_b) -> { ++ return biomesettingsmobs_b.b; ++ }), Codec.DOUBLE.fieldOf("charge").forGetter((biomesettingsmobs_b) -> { ++ return biomesettingsmobs_b.c; ++ })).apply(instance, BiomeSettingsMobs.b::new); ++ }); ++ private final double b; ++ private final double c; ++ ++ private b(double d0, double d1) { ++ this.b = d0; ++ this.c = d1; ++ } ++ ++ public double a() { ++ return this.b; ++ } ++ ++ public double b() { ++ return this.c; ++ } ++ } ++ ++ public static class c extends WeightedRandom.WeightedRandomChoice { ++ ++ public static final Codec b = RecordCodecBuilder.create((instance) -> { ++ return instance.group(IRegistry.ENTITY_TYPE.fieldOf("type").forGetter((biomesettingsmobs_c) -> { ++ return biomesettingsmobs_c.c; ++ }), Codec.INT.fieldOf("weight").forGetter((biomesettingsmobs_c) -> { ++ return biomesettingsmobs_c.a; ++ }), Codec.INT.fieldOf("minCount").forGetter((biomesettingsmobs_c) -> { ++ return biomesettingsmobs_c.d; ++ }), Codec.INT.fieldOf("maxCount").forGetter((biomesettingsmobs_c) -> { ++ return biomesettingsmobs_c.e; ++ })).apply(instance, BiomeSettingsMobs.c::new); ++ }); ++ public final EntityTypes c; ++ public final int d; ++ public final int e; ++ ++ public c(EntityTypes entitytypes, int i, int j, int k) { ++ super(i); ++ this.c = entitytypes.e() == EnumCreatureType.MISC ? EntityTypes.PIG : entitytypes; ++ this.d = j; ++ this.e = k; ++ } ++ ++ public String toString() { ++ return EntityTypes.getName(this.c) + "*(" + this.d + "-" + this.e + "):" + this.a; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerTheEnd.java b/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerTheEnd.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1d46e2c4e06cfe32eac06223e1966ce39c41685e +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerTheEnd.java +@@ -0,0 +1,101 @@ ++package net.minecraft.world.level.biome; ++ ++import com.google.common.collect.ImmutableList; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.codecs.RecordCodecBuilder; ++import java.util.List; ++import net.minecraft.core.IRegistry; ++import net.minecraft.resources.RegistryLookupCodec; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.level.levelgen.SeededRandom; ++import net.minecraft.world.level.levelgen.synth.NoiseGenerator3Handler; ++ ++public class WorldChunkManagerTheEnd extends WorldChunkManager { ++ ++ public static final Codec e = RecordCodecBuilder.create((instance) -> { ++ return instance.group(RegistryLookupCodec.a(IRegistry.ay).forGetter((worldchunkmanagertheend) -> { ++ return worldchunkmanagertheend.g; ++ }), Codec.LONG.fieldOf("seed").stable().forGetter((worldchunkmanagertheend) -> { ++ return worldchunkmanagertheend.h; ++ })).apply(instance, instance.stable(WorldChunkManagerTheEnd::new)); ++ }); ++ private final NoiseGenerator3Handler f; ++ private final IRegistry g; ++ private final long h; ++ private final BiomeBase i; ++ private final BiomeBase j; ++ private final BiomeBase k; ++ private final BiomeBase l; ++ private final BiomeBase m; ++ ++ public WorldChunkManagerTheEnd(IRegistry iregistry, long i) { ++ this(iregistry, i, (BiomeBase) iregistry.d(Biomes.THE_END), (BiomeBase) iregistry.d(Biomes.END_HIGHLANDS), (BiomeBase) iregistry.d(Biomes.END_MIDLANDS), (BiomeBase) iregistry.d(Biomes.SMALL_END_ISLANDS), (BiomeBase) iregistry.d(Biomes.END_BARRENS)); ++ } ++ ++ private WorldChunkManagerTheEnd(IRegistry iregistry, long i, BiomeBase biomebase, BiomeBase biomebase1, BiomeBase biomebase2, BiomeBase biomebase3, BiomeBase biomebase4) { ++ super((List) ImmutableList.of(biomebase, biomebase1, biomebase2, biomebase3, biomebase4)); ++ this.g = iregistry; ++ this.h = i; ++ this.i = biomebase; ++ this.j = biomebase1; ++ this.k = biomebase2; ++ this.l = biomebase3; ++ this.m = biomebase4; ++ SeededRandom seededrandom = new SeededRandom(i); ++ ++ seededrandom.a(17292); ++ this.f = new NoiseGenerator3Handler(seededrandom); ++ } ++ ++ @Override ++ protected Codec a() { ++ return WorldChunkManagerTheEnd.e; ++ } ++ ++ @Override ++ public BiomeBase getBiome(int i, int j, int k) { ++ int l = i >> 2; ++ int i1 = k >> 2; ++ ++ if ((long) l * (long) l + (long) i1 * (long) i1 <= 4096L) { ++ return this.i; ++ } else { ++ float f = a(this.f, l * 2 + 1, i1 * 2 + 1); ++ ++ return f > 40.0F ? this.j : (f >= 0.0F ? this.k : (f < -20.0F ? this.l : this.m)); ++ } ++ } ++ ++ public boolean b(long i) { ++ return this.h == i; ++ } ++ ++ public static float a(NoiseGenerator3Handler noisegenerator3handler, int i, int j) { ++ int k = i / 2; ++ int l = j / 2; ++ int i1 = i % 2; ++ int j1 = j % 2; ++ float f = 100.0F - MathHelper.c((float) (i * i + j * j)) * 8.0F; ++ ++ f = MathHelper.a(f, -100.0F, 80.0F); ++ ++ for (int k1 = -12; k1 <= 12; ++k1) { ++ for (int l1 = -12; l1 <= 12; ++l1) { ++ long i2 = (long) (k + k1); ++ long j2 = (long) (l + l1); ++ ++ if (i2 * i2 + j2 * j2 > 4096L && noisegenerator3handler.a((double) i2, (double) j2) < -0.8999999761581421D) { ++ float f1 = (MathHelper.e((float) i2) * 3439.0F + MathHelper.e((float) j2) * 147.0F) % 13.0F + 9.0F; ++ float f2 = (float) (i1 - k1 * 2); ++ float f3 = (float) (j1 - l1 * 2); ++ float f4 = 100.0F - MathHelper.c(f2 * f2 + f3 * f3) * f1; ++ ++ f4 = MathHelper.a(f4, -100.0F, 80.0F); ++ f = Math.max(f, f4); ++ } ++ } ++ } ++ ++ return f; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/BlockBell.java b/src/main/java/net/minecraft/world/level/block/BlockBell.java +new file mode 100644 +index 0000000000000000000000000000000000000000..687f7acd8254294b568c9adf3e72d02d12551381 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/BlockBell.java +@@ -0,0 +1,256 @@ ++package net.minecraft.world.level.block; ++ ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.stats.StatisticList; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.projectile.IProjectile; ++import net.minecraft.world.item.context.BlockActionContext; ++import net.minecraft.world.level.GeneratorAccess; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.IWorldReader; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.entity.TileEntityBell; ++import net.minecraft.world.level.block.state.BlockBase; ++import net.minecraft.world.level.block.state.BlockStateList; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.block.state.properties.BlockProperties; ++import net.minecraft.world.level.block.state.properties.BlockPropertyBellAttach; ++import net.minecraft.world.level.block.state.properties.BlockStateBoolean; ++import net.minecraft.world.level.block.state.properties.BlockStateDirection; ++import net.minecraft.world.level.block.state.properties.BlockStateEnum; ++import net.minecraft.world.level.material.EnumPistonReaction; ++import net.minecraft.world.level.pathfinder.PathMode; ++import net.minecraft.world.phys.MovingObjectPositionBlock; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapeCollision; ++import net.minecraft.world.phys.shapes.VoxelShapes; ++ ++public class BlockBell extends BlockTileEntity { ++ ++ public static final BlockStateDirection a = BlockFacingHorizontal.FACING; ++ public static final BlockStateEnum b = BlockProperties.R; ++ public static final BlockStateBoolean c = BlockProperties.w; ++ private static final VoxelShape d = Block.a(0.0D, 0.0D, 4.0D, 16.0D, 16.0D, 12.0D); ++ private static final VoxelShape e = Block.a(4.0D, 0.0D, 0.0D, 12.0D, 16.0D, 16.0D); ++ private static final VoxelShape f = Block.a(5.0D, 6.0D, 5.0D, 11.0D, 13.0D, 11.0D); ++ private static final VoxelShape g = Block.a(4.0D, 4.0D, 4.0D, 12.0D, 6.0D, 12.0D); ++ private static final VoxelShape h = VoxelShapes.a(BlockBell.g, BlockBell.f); ++ private static final VoxelShape i = VoxelShapes.a(BlockBell.h, Block.a(7.0D, 13.0D, 0.0D, 9.0D, 15.0D, 16.0D)); ++ private static final VoxelShape j = VoxelShapes.a(BlockBell.h, Block.a(0.0D, 13.0D, 7.0D, 16.0D, 15.0D, 9.0D)); ++ private static final VoxelShape k = VoxelShapes.a(BlockBell.h, Block.a(0.0D, 13.0D, 7.0D, 13.0D, 15.0D, 9.0D)); ++ private static final VoxelShape o = VoxelShapes.a(BlockBell.h, Block.a(3.0D, 13.0D, 7.0D, 16.0D, 15.0D, 9.0D)); ++ private static final VoxelShape p = VoxelShapes.a(BlockBell.h, Block.a(7.0D, 13.0D, 0.0D, 9.0D, 15.0D, 13.0D)); ++ private static final VoxelShape q = VoxelShapes.a(BlockBell.h, Block.a(7.0D, 13.0D, 3.0D, 9.0D, 15.0D, 16.0D)); ++ private static final VoxelShape r = VoxelShapes.a(BlockBell.h, Block.a(7.0D, 13.0D, 7.0D, 9.0D, 16.0D, 9.0D)); ++ ++ public BlockBell(BlockBase.Info blockbase_info) { ++ super(blockbase_info); ++ this.j((IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) this.blockStateList.getBlockData()).set(BlockBell.a, EnumDirection.NORTH)).set(BlockBell.b, BlockPropertyBellAttach.FLOOR)).set(BlockBell.c, false)); ++ } ++ ++ @Override ++ public void doPhysics(IBlockData iblockdata, World world, BlockPosition blockposition, Block block, BlockPosition blockposition1, boolean flag) { ++ boolean flag1 = world.isBlockIndirectlyPowered(blockposition); ++ ++ if (flag1 != (Boolean) iblockdata.get(BlockBell.c)) { ++ if (flag1) { ++ this.a(world, blockposition, (EnumDirection) null); ++ } ++ ++ world.setTypeAndData(blockposition, (IBlockData) iblockdata.set(BlockBell.c, flag1), 3); ++ } ++ ++ } ++ ++ @Override ++ public void a(World world, IBlockData iblockdata, MovingObjectPositionBlock movingobjectpositionblock, IProjectile iprojectile) { ++ Entity entity = iprojectile.getShooter(); ++ EntityHuman entityhuman = entity instanceof EntityHuman ? (EntityHuman) entity : null; ++ ++ this.a(world, iblockdata, movingobjectpositionblock, entityhuman, true); ++ } ++ ++ @Override ++ public EnumInteractionResult interact(IBlockData iblockdata, World world, BlockPosition blockposition, EntityHuman entityhuman, EnumHand enumhand, MovingObjectPositionBlock movingobjectpositionblock) { ++ return this.a(world, iblockdata, movingobjectpositionblock, entityhuman, true) ? EnumInteractionResult.a(world.isClientSide) : EnumInteractionResult.PASS; ++ } ++ ++ public boolean a(World world, IBlockData iblockdata, MovingObjectPositionBlock movingobjectpositionblock, @Nullable EntityHuman entityhuman, boolean flag) { ++ EnumDirection enumdirection = movingobjectpositionblock.getDirection(); ++ BlockPosition blockposition = movingobjectpositionblock.getBlockPosition(); ++ boolean flag1 = !flag || this.a(iblockdata, enumdirection, movingobjectpositionblock.getPos().y - (double) blockposition.getY()); ++ ++ if (flag1) { ++ boolean flag2 = this.a(world, blockposition, enumdirection); ++ ++ if (flag2 && entityhuman != null) { ++ entityhuman.a(StatisticList.BELL_RING); ++ } ++ ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ private boolean a(IBlockData iblockdata, EnumDirection enumdirection, double d0) { ++ if (enumdirection.n() != EnumDirection.EnumAxis.Y && d0 <= 0.8123999834060669D) { ++ EnumDirection enumdirection1 = (EnumDirection) iblockdata.get(BlockBell.a); ++ BlockPropertyBellAttach blockpropertybellattach = (BlockPropertyBellAttach) iblockdata.get(BlockBell.b); ++ ++ switch (blockpropertybellattach) { ++ case FLOOR: ++ return enumdirection1.n() == enumdirection.n(); ++ case SINGLE_WALL: ++ case DOUBLE_WALL: ++ return enumdirection1.n() != enumdirection.n(); ++ case CEILING: ++ return true; ++ default: ++ return false; ++ } ++ } else { ++ return false; ++ } ++ } ++ ++ public boolean a(World world, BlockPosition blockposition, @Nullable EnumDirection enumdirection) { ++ TileEntity tileentity = world.getTileEntity(blockposition); ++ ++ if (!world.isClientSide && tileentity instanceof TileEntityBell) { ++ if (enumdirection == null) { ++ enumdirection = (EnumDirection) world.getType(blockposition).get(BlockBell.a); ++ } ++ ++ ((TileEntityBell) tileentity).a(enumdirection); ++ world.playSound((EntityHuman) null, blockposition, SoundEffects.BLOCK_BELL_USE, SoundCategory.BLOCKS, 2.0F, 1.0F); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ private VoxelShape h(IBlockData iblockdata) { ++ EnumDirection enumdirection = (EnumDirection) iblockdata.get(BlockBell.a); ++ BlockPropertyBellAttach blockpropertybellattach = (BlockPropertyBellAttach) iblockdata.get(BlockBell.b); ++ ++ return blockpropertybellattach == BlockPropertyBellAttach.FLOOR ? (enumdirection != EnumDirection.NORTH && enumdirection != EnumDirection.SOUTH ? BlockBell.e : BlockBell.d) : (blockpropertybellattach == BlockPropertyBellAttach.CEILING ? BlockBell.r : (blockpropertybellattach == BlockPropertyBellAttach.DOUBLE_WALL ? (enumdirection != EnumDirection.NORTH && enumdirection != EnumDirection.SOUTH ? BlockBell.j : BlockBell.i) : (enumdirection == EnumDirection.NORTH ? BlockBell.p : (enumdirection == EnumDirection.SOUTH ? BlockBell.q : (enumdirection == EnumDirection.EAST ? BlockBell.o : BlockBell.k))))); ++ } ++ ++ @Override ++ public VoxelShape c(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition, VoxelShapeCollision voxelshapecollision) { ++ return this.h(iblockdata); ++ } ++ ++ @Override ++ public VoxelShape b(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition, VoxelShapeCollision voxelshapecollision) { ++ return this.h(iblockdata); ++ } ++ ++ @Override ++ public EnumRenderType b(IBlockData iblockdata) { ++ return EnumRenderType.MODEL; ++ } ++ ++ @Nullable ++ @Override ++ public IBlockData getPlacedState(BlockActionContext blockactioncontext) { ++ EnumDirection enumdirection = blockactioncontext.getClickedFace(); ++ BlockPosition blockposition = blockactioncontext.getClickPosition(); ++ World world = blockactioncontext.getWorld(); ++ EnumDirection.EnumAxis enumdirection_enumaxis = enumdirection.n(); ++ IBlockData iblockdata; ++ ++ if (enumdirection_enumaxis == EnumDirection.EnumAxis.Y) { ++ iblockdata = (IBlockData) ((IBlockData) this.getBlockData().set(BlockBell.b, enumdirection == EnumDirection.DOWN ? BlockPropertyBellAttach.CEILING : BlockPropertyBellAttach.FLOOR)).set(BlockBell.a, blockactioncontext.f()); ++ if (iblockdata.canPlace(blockactioncontext.getWorld(), blockposition)) { ++ return iblockdata; ++ } ++ } else { ++ boolean flag = enumdirection_enumaxis == EnumDirection.EnumAxis.X && world.getType(blockposition.west()).d(world, blockposition.west(), EnumDirection.EAST) && world.getType(blockposition.east()).d(world, blockposition.east(), EnumDirection.WEST) || enumdirection_enumaxis == EnumDirection.EnumAxis.Z && world.getType(blockposition.north()).d(world, blockposition.north(), EnumDirection.SOUTH) && world.getType(blockposition.south()).d(world, blockposition.south(), EnumDirection.NORTH); ++ ++ iblockdata = (IBlockData) ((IBlockData) this.getBlockData().set(BlockBell.a, enumdirection.opposite())).set(BlockBell.b, flag ? BlockPropertyBellAttach.DOUBLE_WALL : BlockPropertyBellAttach.SINGLE_WALL); ++ if (iblockdata.canPlace(blockactioncontext.getWorld(), blockactioncontext.getClickPosition())) { ++ return iblockdata; ++ } ++ ++ boolean flag1 = world.getType(blockposition.down()).d(world, blockposition.down(), EnumDirection.UP); ++ ++ iblockdata = (IBlockData) iblockdata.set(BlockBell.b, flag1 ? BlockPropertyBellAttach.FLOOR : BlockPropertyBellAttach.CEILING); ++ if (iblockdata.canPlace(blockactioncontext.getWorld(), blockactioncontext.getClickPosition())) { ++ return iblockdata; ++ } ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public IBlockData updateState(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { ++ BlockPropertyBellAttach blockpropertybellattach = (BlockPropertyBellAttach) iblockdata.get(BlockBell.b); ++ EnumDirection enumdirection1 = l(iblockdata).opposite(); ++ ++ if (enumdirection1 == enumdirection && !iblockdata.canPlace(generatoraccess, blockposition) && blockpropertybellattach != BlockPropertyBellAttach.DOUBLE_WALL) { ++ return Blocks.AIR.getBlockData(); ++ } else { ++ if (enumdirection.n() == ((EnumDirection) iblockdata.get(BlockBell.a)).n()) { ++ if (blockpropertybellattach == BlockPropertyBellAttach.DOUBLE_WALL && !iblockdata1.d(generatoraccess, blockposition1, enumdirection)) { ++ return (IBlockData) ((IBlockData) iblockdata.set(BlockBell.b, BlockPropertyBellAttach.SINGLE_WALL)).set(BlockBell.a, enumdirection.opposite()); ++ } ++ ++ if (blockpropertybellattach == BlockPropertyBellAttach.SINGLE_WALL && enumdirection1.opposite() == enumdirection && iblockdata1.d(generatoraccess, blockposition1, (EnumDirection) iblockdata.get(BlockBell.a))) { ++ return (IBlockData) iblockdata.set(BlockBell.b, BlockPropertyBellAttach.DOUBLE_WALL); ++ } ++ } ++ ++ return super.updateState(iblockdata, enumdirection, iblockdata1, generatoraccess, blockposition, blockposition1); ++ } ++ } ++ ++ @Override ++ public boolean canPlace(IBlockData iblockdata, IWorldReader iworldreader, BlockPosition blockposition) { ++ EnumDirection enumdirection = l(iblockdata).opposite(); ++ ++ return enumdirection == EnumDirection.UP ? Block.a(iworldreader, blockposition.up(), EnumDirection.DOWN) : BlockAttachable.b(iworldreader, blockposition, enumdirection); ++ } ++ ++ private static EnumDirection l(IBlockData iblockdata) { ++ switch ((BlockPropertyBellAttach) iblockdata.get(BlockBell.b)) { ++ case FLOOR: ++ return EnumDirection.UP; ++ case CEILING: ++ return EnumDirection.DOWN; ++ default: ++ return ((EnumDirection) iblockdata.get(BlockBell.a)).opposite(); ++ } ++ } ++ ++ @Override ++ public EnumPistonReaction getPushReaction(IBlockData iblockdata) { ++ return EnumPistonReaction.DESTROY; ++ } ++ ++ @Override ++ protected void a(BlockStateList.a blockstatelist_a) { ++ blockstatelist_a.a(BlockBell.a, BlockBell.b, BlockBell.c); ++ } ++ ++ @Nullable ++ @Override ++ public TileEntity createTile(IBlockAccess iblockaccess) { ++ return new TileEntityBell(); ++ } ++ ++ @Override ++ public boolean a(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition, PathMode pathmode) { ++ return false; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/BlockFlowerPot.java b/src/main/java/net/minecraft/world/level/block/BlockFlowerPot.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a61d1ffeebfd00a5fcd5faf95200b0640da8ea82 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/BlockFlowerPot.java +@@ -0,0 +1,92 @@ ++package net.minecraft.world.level.block; ++ ++import com.google.common.collect.Maps; ++import java.util.Map; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.stats.StatisticList; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.item.ItemBlock; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.GeneratorAccess; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.state.BlockBase; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.pathfinder.PathMode; ++import net.minecraft.world.phys.MovingObjectPositionBlock; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapeCollision; ++ ++public class BlockFlowerPot extends Block { ++ ++ private static final Map b = Maps.newHashMap(); ++ protected static final VoxelShape a = Block.a(5.0D, 0.0D, 5.0D, 11.0D, 6.0D, 11.0D); ++ private final Block c; ++ ++ public BlockFlowerPot(Block block, BlockBase.Info blockbase_info) { ++ super(blockbase_info); ++ this.c = block; ++ BlockFlowerPot.b.put(block, this); ++ } ++ ++ @Override ++ public VoxelShape b(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition, VoxelShapeCollision voxelshapecollision) { ++ return BlockFlowerPot.a; ++ } ++ ++ @Override ++ public EnumRenderType b(IBlockData iblockdata) { ++ return EnumRenderType.MODEL; ++ } ++ ++ @Override ++ public EnumInteractionResult interact(IBlockData iblockdata, World world, BlockPosition blockposition, EntityHuman entityhuman, EnumHand enumhand, MovingObjectPositionBlock movingobjectpositionblock) { ++ ItemStack itemstack = entityhuman.b(enumhand); ++ Item item = itemstack.getItem(); ++ Block block = item instanceof ItemBlock ? (Block) BlockFlowerPot.b.getOrDefault(((ItemBlock) item).getBlock(), Blocks.AIR) : Blocks.AIR; ++ boolean flag = block == Blocks.AIR; ++ boolean flag1 = this.c == Blocks.AIR; ++ ++ if (flag != flag1) { ++ if (flag1) { ++ world.setTypeAndData(blockposition, block.getBlockData(), 3); ++ entityhuman.a(StatisticList.POT_FLOWER); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } ++ } else { ++ ItemStack itemstack1 = new ItemStack(this.c); ++ ++ if (itemstack.isEmpty()) { ++ entityhuman.a(enumhand, itemstack1); ++ } else if (!entityhuman.g(itemstack1)) { ++ entityhuman.drop(itemstack1, false); ++ } ++ ++ world.setTypeAndData(blockposition, Blocks.FLOWER_POT.getBlockData(), 3); ++ } ++ ++ return EnumInteractionResult.a(world.isClientSide); ++ } else { ++ return EnumInteractionResult.CONSUME; ++ } ++ } ++ ++ @Override ++ public IBlockData updateState(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { ++ return enumdirection == EnumDirection.DOWN && !iblockdata.canPlace(generatoraccess, blockposition) ? Blocks.AIR.getBlockData() : super.updateState(iblockdata, enumdirection, iblockdata1, generatoraccess, blockposition, blockposition1); ++ } ++ ++ public Block c() { ++ return this.c; ++ } ++ ++ @Override ++ public boolean a(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition, PathMode pathmode) { ++ return false; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/BlockIceFrost.java b/src/main/java/net/minecraft/world/level/block/BlockIceFrost.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7239a30bd4a5dc4ed09802eea8f7126485ebb635 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/BlockIceFrost.java +@@ -0,0 +1,100 @@ ++package net.minecraft.world.level.block; ++ ++import java.util.Random; ++import net.minecraft.core.BaseBlockPosition; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.state.BlockBase; ++import net.minecraft.world.level.block.state.BlockStateList; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.block.state.properties.BlockProperties; ++import net.minecraft.world.level.block.state.properties.BlockStateInteger; ++ ++public class BlockIceFrost extends BlockIce { ++ ++ public static final BlockStateInteger a = BlockProperties.ag; ++ ++ public BlockIceFrost(BlockBase.Info blockbase_info) { ++ super(blockbase_info); ++ this.j((IBlockData) ((IBlockData) this.blockStateList.getBlockData()).set(BlockIceFrost.a, 0)); ++ } ++ ++ @Override ++ public void tick(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, Random random) { ++ this.tickAlways(iblockdata, worldserver, blockposition, random); ++ } ++ ++ @Override ++ public void tickAlways(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, Random random) { ++ if ((random.nextInt(3) == 0 || this.a(worldserver, blockposition, 4)) && worldserver.getLightLevel(blockposition) > 11 - (Integer) iblockdata.get(BlockIceFrost.a) - iblockdata.b((IBlockAccess) worldserver, blockposition) && this.e(iblockdata, (World) worldserver, blockposition)) { ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ EnumDirection[] aenumdirection = EnumDirection.values(); ++ int i = aenumdirection.length; ++ ++ for (int j = 0; j < i; ++j) { ++ EnumDirection enumdirection = aenumdirection[j]; ++ ++ blockposition_mutableblockposition.a((BaseBlockPosition) blockposition, enumdirection); ++ IBlockData iblockdata1 = worldserver.getType(blockposition_mutableblockposition); ++ ++ if (iblockdata1.a((Block) this) && !this.e(iblockdata1, (World) worldserver, blockposition_mutableblockposition)) { ++ worldserver.getBlockTickList().a(blockposition_mutableblockposition, this, MathHelper.nextInt(random, 20, 40)); ++ } ++ } ++ ++ } else { ++ worldserver.getBlockTickList().a(blockposition, this, MathHelper.nextInt(random, 20, 40)); ++ } ++ } ++ ++ private boolean e(IBlockData iblockdata, World world, BlockPosition blockposition) { ++ int i = (Integer) iblockdata.get(BlockIceFrost.a); ++ ++ if (i < 3) { ++ world.setTypeAndData(blockposition, (IBlockData) iblockdata.set(BlockIceFrost.a, i + 1), 2); ++ return false; ++ } else { ++ this.melt(iblockdata, world, blockposition); ++ return true; ++ } ++ } ++ ++ @Override ++ public void doPhysics(IBlockData iblockdata, World world, BlockPosition blockposition, Block block, BlockPosition blockposition1, boolean flag) { ++ if (block == this && this.a(world, blockposition, 2)) { ++ this.melt(iblockdata, world, blockposition); ++ } ++ ++ super.doPhysics(iblockdata, world, blockposition, block, blockposition1, flag); ++ } ++ ++ private boolean a(IBlockAccess iblockaccess, BlockPosition blockposition, int i) { ++ int j = 0; ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ EnumDirection[] aenumdirection = EnumDirection.values(); ++ int k = aenumdirection.length; ++ ++ for (int l = 0; l < k; ++l) { ++ EnumDirection enumdirection = aenumdirection[l]; ++ ++ blockposition_mutableblockposition.a((BaseBlockPosition) blockposition, enumdirection); ++ if (iblockaccess.getType(blockposition_mutableblockposition).a((Block) this)) { ++ ++j; ++ if (j >= i) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ @Override ++ protected void a(BlockStateList.a blockstatelist_a) { ++ blockstatelist_a.a(BlockIceFrost.a); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/BlockMinecartTrackAbstract.java b/src/main/java/net/minecraft/world/level/block/BlockMinecartTrackAbstract.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f65a53347f26affd1ce8d79527a6486e6bf8fbdd +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/BlockMinecartTrackAbstract.java +@@ -0,0 +1,147 @@ ++package net.minecraft.world.level.block; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsBlock; ++import net.minecraft.world.item.context.BlockActionContext; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.IWorldReader; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.state.BlockBase; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.block.state.properties.BlockPropertyTrackPosition; ++import net.minecraft.world.level.block.state.properties.IBlockState; ++import net.minecraft.world.level.material.EnumPistonReaction; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapeCollision; ++ ++public abstract class BlockMinecartTrackAbstract extends Block { ++ ++ protected static final VoxelShape a = Block.a(0.0D, 0.0D, 0.0D, 16.0D, 2.0D, 16.0D); ++ protected static final VoxelShape b = Block.a(0.0D, 0.0D, 0.0D, 16.0D, 8.0D, 16.0D); ++ private final boolean c; ++ ++ public static boolean a(World world, BlockPosition blockposition) { ++ return g(world.getType(blockposition)); ++ } ++ ++ public static boolean g(IBlockData iblockdata) { ++ return iblockdata.a((Tag) TagsBlock.RAILS) && iblockdata.getBlock() instanceof BlockMinecartTrackAbstract; ++ } ++ ++ protected BlockMinecartTrackAbstract(boolean flag, BlockBase.Info blockbase_info) { ++ super(blockbase_info); ++ this.c = flag; ++ } ++ ++ public boolean c() { ++ return this.c; ++ } ++ ++ @Override ++ public VoxelShape b(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition, VoxelShapeCollision voxelshapecollision) { ++ BlockPropertyTrackPosition blockpropertytrackposition = iblockdata.a((Block) this) ? (BlockPropertyTrackPosition) iblockdata.get(this.d()) : null; ++ ++ return blockpropertytrackposition != null && blockpropertytrackposition.c() ? BlockMinecartTrackAbstract.b : BlockMinecartTrackAbstract.a; ++ } ++ ++ @Override ++ public boolean canPlace(IBlockData iblockdata, IWorldReader iworldreader, BlockPosition blockposition) { ++ return c((IBlockAccess) iworldreader, blockposition.down()); ++ } ++ ++ @Override ++ public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { ++ if (!iblockdata1.a(iblockdata.getBlock())) { ++ this.a(iblockdata, world, blockposition, flag); ++ } ++ } ++ ++ protected IBlockData a(IBlockData iblockdata, World world, BlockPosition blockposition, boolean flag) { ++ iblockdata = this.a(world, blockposition, iblockdata, true); ++ if (this.c) { ++ iblockdata.doPhysics(world, blockposition, this, blockposition, flag); ++ } ++ ++ return iblockdata; ++ } ++ ++ @Override ++ public void doPhysics(IBlockData iblockdata, World world, BlockPosition blockposition, Block block, BlockPosition blockposition1, boolean flag) { ++ if (!world.isClientSide && world.getType(blockposition).a((Block) this)) { ++ BlockPropertyTrackPosition blockpropertytrackposition = (BlockPropertyTrackPosition) iblockdata.get(this.d()); ++ ++ if (a(blockposition, world, blockpropertytrackposition)) { ++ c(iblockdata, world, blockposition); ++ world.a(blockposition, flag); ++ } else { ++ this.a(iblockdata, world, blockposition, block); ++ } ++ ++ } ++ } ++ ++ private static boolean a(BlockPosition blockposition, World world, BlockPropertyTrackPosition blockpropertytrackposition) { ++ if (!c((IBlockAccess) world, blockposition.down())) { ++ return true; ++ } else { ++ switch (blockpropertytrackposition) { ++ case ASCENDING_EAST: ++ return !c((IBlockAccess) world, blockposition.east()); ++ case ASCENDING_WEST: ++ return !c((IBlockAccess) world, blockposition.west()); ++ case ASCENDING_NORTH: ++ return !c((IBlockAccess) world, blockposition.north()); ++ case ASCENDING_SOUTH: ++ return !c((IBlockAccess) world, blockposition.south()); ++ default: ++ return false; ++ } ++ } ++ } ++ ++ protected void a(IBlockData iblockdata, World world, BlockPosition blockposition, Block block) {} ++ ++ protected IBlockData a(World world, BlockPosition blockposition, IBlockData iblockdata, boolean flag) { ++ if (world.isClientSide) { ++ return iblockdata; ++ } else { ++ BlockPropertyTrackPosition blockpropertytrackposition = (BlockPropertyTrackPosition) iblockdata.get(this.d()); ++ ++ return (new MinecartTrackLogic(world, blockposition, iblockdata)).a(world.isBlockIndirectlyPowered(blockposition), flag, blockpropertytrackposition).c(); ++ } ++ } ++ ++ @Override ++ public EnumPistonReaction getPushReaction(IBlockData iblockdata) { ++ return EnumPistonReaction.NORMAL; ++ } ++ ++ @Override ++ public void remove(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { ++ if (!flag) { ++ super.remove(iblockdata, world, blockposition, iblockdata1, flag); ++ if (((BlockPropertyTrackPosition) iblockdata.get(this.d())).c()) { ++ world.applyPhysics(blockposition.up(), this); ++ } ++ ++ if (this.c) { ++ world.applyPhysics(blockposition, this); ++ world.applyPhysics(blockposition.down(), this); ++ } ++ ++ } ++ } ++ ++ @Override ++ public IBlockData getPlacedState(BlockActionContext blockactioncontext) { ++ IBlockData iblockdata = super.getBlockData(); ++ EnumDirection enumdirection = blockactioncontext.f(); ++ boolean flag = enumdirection == EnumDirection.EAST || enumdirection == EnumDirection.WEST; ++ ++ return (IBlockData) iblockdata.set(this.d(), flag ? BlockPropertyTrackPosition.EAST_WEST : BlockPropertyTrackPosition.NORTH_SOUTH); ++ } ++ ++ public abstract IBlockState d(); ++} +diff --git a/src/main/java/net/minecraft/world/level/block/BlockPumpkin.java b/src/main/java/net/minecraft/world/level/block/BlockPumpkin.java +new file mode 100644 +index 0000000000000000000000000000000000000000..130b768ac7155c2960694dfa12163e46315f7797 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/BlockPumpkin.java +@@ -0,0 +1,59 @@ ++package net.minecraft.world.level.block; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; ++import net.minecraft.world.entity.item.EntityItem; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.state.BlockBase; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.phys.MovingObjectPositionBlock; ++ ++public class BlockPumpkin extends BlockStemmed { ++ ++ protected BlockPumpkin(BlockBase.Info blockbase_info) { ++ super(blockbase_info); ++ } ++ ++ @Override ++ public EnumInteractionResult interact(IBlockData iblockdata, World world, BlockPosition blockposition, EntityHuman entityhuman, EnumHand enumhand, MovingObjectPositionBlock movingobjectpositionblock) { ++ ItemStack itemstack = entityhuman.b(enumhand); ++ ++ if (itemstack.getItem() == Items.SHEARS) { ++ if (!world.isClientSide) { ++ EnumDirection enumdirection = movingobjectpositionblock.getDirection(); ++ EnumDirection enumdirection1 = enumdirection.n() == EnumDirection.EnumAxis.Y ? entityhuman.getDirection().opposite() : enumdirection; ++ ++ world.playSound((EntityHuman) null, blockposition, SoundEffects.BLOCK_PUMPKIN_CARVE, SoundCategory.BLOCKS, 1.0F, 1.0F); ++ world.setTypeAndData(blockposition, (IBlockData) Blocks.CARVED_PUMPKIN.getBlockData().set(BlockPumpkinCarved.a, enumdirection1), 11); ++ EntityItem entityitem = new EntityItem(world, (double) blockposition.getX() + 0.5D + (double) enumdirection1.getAdjacentX() * 0.65D, (double) blockposition.getY() + 0.1D, (double) blockposition.getZ() + 0.5D + (double) enumdirection1.getAdjacentZ() * 0.65D, new ItemStack(Items.PUMPKIN_SEEDS, 4)); ++ ++ entityitem.setMot(0.05D * (double) enumdirection1.getAdjacentX() + world.random.nextDouble() * 0.02D, 0.05D, 0.05D * (double) enumdirection1.getAdjacentZ() + world.random.nextDouble() * 0.02D); ++ world.addEntity(entityitem); ++ itemstack.damage(1, entityhuman, (entityhuman1) -> { ++ entityhuman1.broadcastItemBreak(enumhand); ++ }); ++ } ++ ++ return EnumInteractionResult.a(world.isClientSide); ++ } else { ++ return super.interact(iblockdata, world, blockposition, entityhuman, enumhand, movingobjectpositionblock); ++ } ++ } ++ ++ @Override ++ public BlockStem c() { ++ return (BlockStem) Blocks.PUMPKIN_STEM; ++ } ++ ++ @Override ++ public BlockStemAttached d() { ++ return (BlockStemAttached) Blocks.ATTACHED_PUMPKIN_STEM; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/BlockTarget.java b/src/main/java/net/minecraft/world/level/block/BlockTarget.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c336490815dc17991d3d84d8c6f0fc58571a3e3a +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/BlockTarget.java +@@ -0,0 +1,115 @@ ++package net.minecraft.world.level.block; ++ ++import java.util.Random; ++import net.minecraft.advancements.CriterionTriggers; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.stats.StatisticList; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.projectile.EntityArrow; ++import net.minecraft.world.entity.projectile.IProjectile; ++import net.minecraft.world.level.GeneratorAccess; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.state.BlockBase; ++import net.minecraft.world.level.block.state.BlockStateList; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.block.state.properties.BlockProperties; ++import net.minecraft.world.level.block.state.properties.BlockStateInteger; ++import net.minecraft.world.phys.MovingObjectPositionBlock; ++import net.minecraft.world.phys.Vec3D; ++ ++public class BlockTarget extends Block { ++ ++ private static final BlockStateInteger a = BlockProperties.az; ++ ++ public BlockTarget(BlockBase.Info blockbase_info) { ++ super(blockbase_info); ++ this.j((IBlockData) ((IBlockData) this.blockStateList.getBlockData()).set(BlockTarget.a, 0)); ++ } ++ ++ @Override ++ public void a(World world, IBlockData iblockdata, MovingObjectPositionBlock movingobjectpositionblock, IProjectile iprojectile) { ++ int i = a((GeneratorAccess) world, iblockdata, movingobjectpositionblock, (Entity) iprojectile); ++ Entity entity = iprojectile.getShooter(); ++ ++ if (entity instanceof EntityPlayer) { ++ EntityPlayer entityplayer = (EntityPlayer) entity; ++ ++ entityplayer.a(StatisticList.TARGET_HIT); ++ CriterionTriggers.L.a(entityplayer, iprojectile, movingobjectpositionblock.getPos(), i); ++ } ++ ++ } ++ ++ private static int a(GeneratorAccess generatoraccess, IBlockData iblockdata, MovingObjectPositionBlock movingobjectpositionblock, Entity entity) { ++ int i = a(movingobjectpositionblock, movingobjectpositionblock.getPos()); ++ int j = entity instanceof EntityArrow ? 20 : 8; ++ ++ if (!generatoraccess.getBlockTickList().a(movingobjectpositionblock.getBlockPosition(), iblockdata.getBlock())) { ++ a(generatoraccess, iblockdata, i, movingobjectpositionblock.getBlockPosition(), j); ++ } ++ ++ return i; ++ } ++ ++ private static int a(MovingObjectPositionBlock movingobjectpositionblock, Vec3D vec3d) { ++ EnumDirection enumdirection = movingobjectpositionblock.getDirection(); ++ double d0 = Math.abs(MathHelper.h(vec3d.x) - 0.5D); ++ double d1 = Math.abs(MathHelper.h(vec3d.y) - 0.5D); ++ double d2 = Math.abs(MathHelper.h(vec3d.z) - 0.5D); ++ EnumDirection.EnumAxis enumdirection_enumaxis = enumdirection.n(); ++ double d3; ++ ++ if (enumdirection_enumaxis == EnumDirection.EnumAxis.Y) { ++ d3 = Math.max(d0, d2); ++ } else if (enumdirection_enumaxis == EnumDirection.EnumAxis.Z) { ++ d3 = Math.max(d0, d1); ++ } else { ++ d3 = Math.max(d1, d2); ++ } ++ ++ return Math.max(1, MathHelper.f(15.0D * MathHelper.a((0.5D - d3) / 0.5D, 0.0D, 1.0D))); ++ } ++ ++ private static void a(GeneratorAccess generatoraccess, IBlockData iblockdata, int i, BlockPosition blockposition, int j) { ++ generatoraccess.setTypeAndData(blockposition, (IBlockData) iblockdata.set(BlockTarget.a, i), 3); ++ generatoraccess.getBlockTickList().a(blockposition, iblockdata.getBlock(), j); ++ } ++ ++ @Override ++ public void tickAlways(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, Random random) { ++ if ((Integer) iblockdata.get(BlockTarget.a) != 0) { ++ worldserver.setTypeAndData(blockposition, (IBlockData) iblockdata.set(BlockTarget.a, 0), 3); ++ } ++ ++ } ++ ++ @Override ++ public int a(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) { ++ return (Integer) iblockdata.get(BlockTarget.a); ++ } ++ ++ @Override ++ public boolean isPowerSource(IBlockData iblockdata) { ++ return true; ++ } ++ ++ @Override ++ protected void a(BlockStateList.a blockstatelist_a) { ++ blockstatelist_a.a(BlockTarget.a); ++ } ++ ++ @Override ++ public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { ++ if (!world.s_() && !iblockdata.a(iblockdata1.getBlock())) { ++ if ((Integer) iblockdata.get(BlockTarget.a) > 0 && !world.getBlockTickList().a(blockposition, this)) { ++ world.setTypeAndData(blockposition, (IBlockData) iblockdata.set(BlockTarget.a, 0), 18); ++ } ++ ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/DoubleBlockFinder.java b/src/main/java/net/minecraft/world/level/block/DoubleBlockFinder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d51f89fed6129c4b37ef63971f8f61dc14e8032d +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/DoubleBlockFinder.java +@@ -0,0 +1,108 @@ ++package net.minecraft.world.level.block; ++ ++import java.util.function.BiPredicate; ++import java.util.function.Function; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.world.level.GeneratorAccess; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.entity.TileEntityTypes; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.block.state.properties.BlockStateDirection; ++ ++public class DoubleBlockFinder { ++ ++ public static DoubleBlockFinder.Result a(TileEntityTypes tileentitytypes, Function function, Function function1, BlockStateDirection blockstatedirection, IBlockData iblockdata, GeneratorAccess generatoraccess, BlockPosition blockposition, BiPredicate bipredicate) { ++ S s0 = tileentitytypes.a((IBlockAccess) generatoraccess, blockposition); ++ ++ if (s0 == null) { ++ return DoubleBlockFinder.Combiner::b; ++ } else if (bipredicate.test(generatoraccess, blockposition)) { ++ return DoubleBlockFinder.Combiner::b; ++ } else { ++ DoubleBlockFinder.BlockType doubleblockfinder_blocktype = (DoubleBlockFinder.BlockType) function.apply(iblockdata); ++ boolean flag = doubleblockfinder_blocktype == DoubleBlockFinder.BlockType.SINGLE; ++ boolean flag1 = doubleblockfinder_blocktype == DoubleBlockFinder.BlockType.FIRST; ++ ++ if (flag) { ++ return new DoubleBlockFinder.Result.Single<>(s0); ++ } else { ++ BlockPosition blockposition1 = blockposition.shift((EnumDirection) function1.apply(iblockdata)); ++ IBlockData iblockdata1 = generatoraccess.getType(blockposition1); ++ ++ if (iblockdata1.a(iblockdata.getBlock())) { ++ DoubleBlockFinder.BlockType doubleblockfinder_blocktype1 = (DoubleBlockFinder.BlockType) function.apply(iblockdata1); ++ ++ if (doubleblockfinder_blocktype1 != DoubleBlockFinder.BlockType.SINGLE && doubleblockfinder_blocktype != doubleblockfinder_blocktype1 && iblockdata1.get(blockstatedirection) == iblockdata.get(blockstatedirection)) { ++ if (bipredicate.test(generatoraccess, blockposition1)) { ++ return DoubleBlockFinder.Combiner::b; ++ } ++ ++ S s1 = tileentitytypes.a((IBlockAccess) generatoraccess, blockposition1); ++ ++ if (s1 != null) { ++ S s2 = flag1 ? s0 : s1; ++ S s3 = flag1 ? s1 : s0; ++ ++ return new DoubleBlockFinder.Result.Double<>(s2, s3); ++ } ++ } ++ } ++ ++ return new DoubleBlockFinder.Result.Single<>(s0); ++ } ++ } ++ } ++ ++ public interface Result { ++ ++ T apply(DoubleBlockFinder.Combiner doubleblockfinder_combiner); ++ ++ public static final class Single implements DoubleBlockFinder.Result { ++ ++ private final S a; ++ ++ public Single(S s0) { ++ this.a = s0; ++ } ++ ++ @Override ++ public T apply(DoubleBlockFinder.Combiner doubleblockfinder_combiner) { ++ return doubleblockfinder_combiner.a(this.a); ++ } ++ } ++ ++ public static final class Double implements DoubleBlockFinder.Result { ++ ++ private final S a; ++ private final S b; ++ ++ public Double(S s0, S s1) { ++ this.a = s0; ++ this.b = s1; ++ } ++ ++ @Override ++ public T apply(DoubleBlockFinder.Combiner doubleblockfinder_combiner) { ++ return doubleblockfinder_combiner.a(this.a, this.b); ++ } ++ } ++ } ++ ++ public interface Combiner { ++ ++ T a(S s0, S s1); ++ ++ T a(S s0); ++ ++ T b(); ++ } ++ ++ public static enum BlockType { ++ ++ SINGLE, FIRST, SECOND; ++ ++ private BlockType() {} ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/MinecartTrackLogic.java b/src/main/java/net/minecraft/world/level/block/MinecartTrackLogic.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8a8e3af0290a9483ee59d5fab061a9a9f5613f0a +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/MinecartTrackLogic.java +@@ -0,0 +1,369 @@ ++package net.minecraft.world.level.block; ++ ++import com.google.common.collect.Lists; ++import java.util.Iterator; ++import java.util.List; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.block.state.properties.BlockPropertyTrackPosition; ++ ++public class MinecartTrackLogic { ++ ++ private final World a; ++ private final BlockPosition b; ++ private final BlockMinecartTrackAbstract c; ++ private IBlockData d; ++ private final boolean e; ++ private final List f = Lists.newArrayList(); ++ ++ public MinecartTrackLogic(World world, BlockPosition blockposition, IBlockData iblockdata) { ++ this.a = world; ++ this.b = blockposition; ++ this.d = iblockdata; ++ this.c = (BlockMinecartTrackAbstract) iblockdata.getBlock(); ++ BlockPropertyTrackPosition blockpropertytrackposition = (BlockPropertyTrackPosition) iblockdata.get(this.c.d()); ++ ++ this.e = this.c.c(); ++ this.a(blockpropertytrackposition); ++ } ++ ++ public List a() { ++ return this.f; ++ } ++ ++ private void a(BlockPropertyTrackPosition blockpropertytrackposition) { ++ this.f.clear(); ++ switch (blockpropertytrackposition) { ++ case NORTH_SOUTH: ++ this.f.add(this.b.north()); ++ this.f.add(this.b.south()); ++ break; ++ case EAST_WEST: ++ this.f.add(this.b.west()); ++ this.f.add(this.b.east()); ++ break; ++ case ASCENDING_EAST: ++ this.f.add(this.b.west()); ++ this.f.add(this.b.east().up()); ++ break; ++ case ASCENDING_WEST: ++ this.f.add(this.b.west().up()); ++ this.f.add(this.b.east()); ++ break; ++ case ASCENDING_NORTH: ++ this.f.add(this.b.north().up()); ++ this.f.add(this.b.south()); ++ break; ++ case ASCENDING_SOUTH: ++ this.f.add(this.b.north()); ++ this.f.add(this.b.south().up()); ++ break; ++ case SOUTH_EAST: ++ this.f.add(this.b.east()); ++ this.f.add(this.b.south()); ++ break; ++ case SOUTH_WEST: ++ this.f.add(this.b.west()); ++ this.f.add(this.b.south()); ++ break; ++ case NORTH_WEST: ++ this.f.add(this.b.west()); ++ this.f.add(this.b.north()); ++ break; ++ case NORTH_EAST: ++ this.f.add(this.b.east()); ++ this.f.add(this.b.north()); ++ } ++ ++ } ++ ++ private void d() { ++ for (int i = 0; i < this.f.size(); ++i) { ++ MinecartTrackLogic minecarttracklogic = this.b((BlockPosition) this.f.get(i)); ++ ++ if (minecarttracklogic != null && minecarttracklogic.a(this)) { ++ this.f.set(i, minecarttracklogic.b); ++ } else { ++ this.f.remove(i--); ++ } ++ } ++ ++ } ++ ++ private boolean a(BlockPosition blockposition) { ++ return BlockMinecartTrackAbstract.a(this.a, blockposition) || BlockMinecartTrackAbstract.a(this.a, blockposition.up()) || BlockMinecartTrackAbstract.a(this.a, blockposition.down()); ++ } ++ ++ @Nullable ++ private MinecartTrackLogic b(BlockPosition blockposition) { ++ IBlockData iblockdata = this.a.getType(blockposition); ++ ++ if (BlockMinecartTrackAbstract.g(iblockdata)) { ++ return new MinecartTrackLogic(this.a, blockposition, iblockdata); ++ } else { ++ BlockPosition blockposition1 = blockposition.up(); ++ ++ iblockdata = this.a.getType(blockposition1); ++ if (BlockMinecartTrackAbstract.g(iblockdata)) { ++ return new MinecartTrackLogic(this.a, blockposition1, iblockdata); ++ } else { ++ blockposition1 = blockposition.down(); ++ iblockdata = this.a.getType(blockposition1); ++ return BlockMinecartTrackAbstract.g(iblockdata) ? new MinecartTrackLogic(this.a, blockposition1, iblockdata) : null; ++ } ++ } ++ } ++ ++ private boolean a(MinecartTrackLogic minecarttracklogic) { ++ return this.c(minecarttracklogic.b); ++ } ++ ++ private boolean c(BlockPosition blockposition) { ++ for (int i = 0; i < this.f.size(); ++i) { ++ BlockPosition blockposition1 = (BlockPosition) this.f.get(i); ++ ++ if (blockposition1.getX() == blockposition.getX() && blockposition1.getZ() == blockposition.getZ()) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ protected int b() { ++ int i = 0; ++ Iterator iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator(); ++ ++ while (iterator.hasNext()) { ++ EnumDirection enumdirection = (EnumDirection) iterator.next(); ++ ++ if (this.a(this.b.shift(enumdirection))) { ++ ++i; ++ } ++ } ++ ++ return i; ++ } ++ ++ private boolean b(MinecartTrackLogic minecarttracklogic) { ++ return this.a(minecarttracklogic) || this.f.size() != 2; ++ } ++ ++ private void c(MinecartTrackLogic minecarttracklogic) { ++ this.f.add(minecarttracklogic.b); ++ BlockPosition blockposition = this.b.north(); ++ BlockPosition blockposition1 = this.b.south(); ++ BlockPosition blockposition2 = this.b.west(); ++ BlockPosition blockposition3 = this.b.east(); ++ boolean flag = this.c(blockposition); ++ boolean flag1 = this.c(blockposition1); ++ boolean flag2 = this.c(blockposition2); ++ boolean flag3 = this.c(blockposition3); ++ BlockPropertyTrackPosition blockpropertytrackposition = null; ++ ++ if (flag || flag1) { ++ blockpropertytrackposition = BlockPropertyTrackPosition.NORTH_SOUTH; ++ } ++ ++ if (flag2 || flag3) { ++ blockpropertytrackposition = BlockPropertyTrackPosition.EAST_WEST; ++ } ++ ++ if (!this.e) { ++ if (flag1 && flag3 && !flag && !flag2) { ++ blockpropertytrackposition = BlockPropertyTrackPosition.SOUTH_EAST; ++ } ++ ++ if (flag1 && flag2 && !flag && !flag3) { ++ blockpropertytrackposition = BlockPropertyTrackPosition.SOUTH_WEST; ++ } ++ ++ if (flag && flag2 && !flag1 && !flag3) { ++ blockpropertytrackposition = BlockPropertyTrackPosition.NORTH_WEST; ++ } ++ ++ if (flag && flag3 && !flag1 && !flag2) { ++ blockpropertytrackposition = BlockPropertyTrackPosition.NORTH_EAST; ++ } ++ } ++ ++ if (blockpropertytrackposition == BlockPropertyTrackPosition.NORTH_SOUTH) { ++ if (BlockMinecartTrackAbstract.a(this.a, blockposition.up())) { ++ blockpropertytrackposition = BlockPropertyTrackPosition.ASCENDING_NORTH; ++ } ++ ++ if (BlockMinecartTrackAbstract.a(this.a, blockposition1.up())) { ++ blockpropertytrackposition = BlockPropertyTrackPosition.ASCENDING_SOUTH; ++ } ++ } ++ ++ if (blockpropertytrackposition == BlockPropertyTrackPosition.EAST_WEST) { ++ if (BlockMinecartTrackAbstract.a(this.a, blockposition3.up())) { ++ blockpropertytrackposition = BlockPropertyTrackPosition.ASCENDING_EAST; ++ } ++ ++ if (BlockMinecartTrackAbstract.a(this.a, blockposition2.up())) { ++ blockpropertytrackposition = BlockPropertyTrackPosition.ASCENDING_WEST; ++ } ++ } ++ ++ if (blockpropertytrackposition == null) { ++ blockpropertytrackposition = BlockPropertyTrackPosition.NORTH_SOUTH; ++ } ++ ++ this.d = (IBlockData) this.d.set(this.c.d(), blockpropertytrackposition); ++ this.a.setTypeAndData(this.b, this.d, 3); ++ } ++ ++ private boolean d(BlockPosition blockposition) { ++ MinecartTrackLogic minecarttracklogic = this.b(blockposition); ++ ++ if (minecarttracklogic == null) { ++ return false; ++ } else { ++ minecarttracklogic.d(); ++ return minecarttracklogic.b(this); ++ } ++ } ++ ++ public MinecartTrackLogic a(boolean flag, boolean flag1, BlockPropertyTrackPosition blockpropertytrackposition) { ++ BlockPosition blockposition = this.b.north(); ++ BlockPosition blockposition1 = this.b.south(); ++ BlockPosition blockposition2 = this.b.west(); ++ BlockPosition blockposition3 = this.b.east(); ++ boolean flag2 = this.d(blockposition); ++ boolean flag3 = this.d(blockposition1); ++ boolean flag4 = this.d(blockposition2); ++ boolean flag5 = this.d(blockposition3); ++ BlockPropertyTrackPosition blockpropertytrackposition1 = null; ++ boolean flag6 = flag2 || flag3; ++ boolean flag7 = flag4 || flag5; ++ ++ if (flag6 && !flag7) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.NORTH_SOUTH; ++ } ++ ++ if (flag7 && !flag6) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.EAST_WEST; ++ } ++ ++ boolean flag8 = flag3 && flag5; ++ boolean flag9 = flag3 && flag4; ++ boolean flag10 = flag2 && flag5; ++ boolean flag11 = flag2 && flag4; ++ ++ if (!this.e) { ++ if (flag8 && !flag2 && !flag4) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.SOUTH_EAST; ++ } ++ ++ if (flag9 && !flag2 && !flag5) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.SOUTH_WEST; ++ } ++ ++ if (flag11 && !flag3 && !flag5) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.NORTH_WEST; ++ } ++ ++ if (flag10 && !flag3 && !flag4) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.NORTH_EAST; ++ } ++ } ++ ++ if (blockpropertytrackposition1 == null) { ++ if (flag6 && flag7) { ++ blockpropertytrackposition1 = blockpropertytrackposition; ++ } else if (flag6) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.NORTH_SOUTH; ++ } else if (flag7) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.EAST_WEST; ++ } ++ ++ if (!this.e) { ++ if (flag) { ++ if (flag8) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.SOUTH_EAST; ++ } ++ ++ if (flag9) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.SOUTH_WEST; ++ } ++ ++ if (flag10) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.NORTH_EAST; ++ } ++ ++ if (flag11) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.NORTH_WEST; ++ } ++ } else { ++ if (flag11) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.NORTH_WEST; ++ } ++ ++ if (flag10) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.NORTH_EAST; ++ } ++ ++ if (flag9) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.SOUTH_WEST; ++ } ++ ++ if (flag8) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.SOUTH_EAST; ++ } ++ } ++ } ++ } ++ ++ if (blockpropertytrackposition1 == BlockPropertyTrackPosition.NORTH_SOUTH) { ++ if (BlockMinecartTrackAbstract.a(this.a, blockposition.up())) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.ASCENDING_NORTH; ++ } ++ ++ if (BlockMinecartTrackAbstract.a(this.a, blockposition1.up())) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.ASCENDING_SOUTH; ++ } ++ } ++ ++ if (blockpropertytrackposition1 == BlockPropertyTrackPosition.EAST_WEST) { ++ if (BlockMinecartTrackAbstract.a(this.a, blockposition3.up())) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.ASCENDING_EAST; ++ } ++ ++ if (BlockMinecartTrackAbstract.a(this.a, blockposition2.up())) { ++ blockpropertytrackposition1 = BlockPropertyTrackPosition.ASCENDING_WEST; ++ } ++ } ++ ++ if (blockpropertytrackposition1 == null) { ++ blockpropertytrackposition1 = blockpropertytrackposition; ++ } ++ ++ this.a(blockpropertytrackposition1); ++ this.d = (IBlockData) this.d.set(this.c.d(), blockpropertytrackposition1); ++ if (flag1 || this.a.getType(this.b) != this.d) { ++ this.a.setTypeAndData(this.b, this.d, 3); ++ ++ for (int i = 0; i < this.f.size(); ++i) { ++ MinecartTrackLogic minecarttracklogic = this.b((BlockPosition) this.f.get(i)); ++ ++ if (minecarttracklogic != null) { ++ minecarttracklogic.d(); ++ if (minecarttracklogic.b(this)) { ++ minecarttracklogic.c(this); ++ } ++ } ++ } ++ } ++ ++ return this; ++ } ++ ++ public IBlockData c() { ++ return this.d; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/SoundEffectType.java b/src/main/java/net/minecraft/world/level/block/SoundEffectType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0987b25ac586d5d7b7954256c740fdf736498dae +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/SoundEffectType.java +@@ -0,0 +1,92 @@ ++package net.minecraft.world.level.block; ++ ++import net.minecraft.sounds.SoundEffect; ++import net.minecraft.sounds.SoundEffects; ++ ++public class SoundEffectType { ++ ++ public static final SoundEffectType a = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_WOOD_BREAK, SoundEffects.BLOCK_WOOD_STEP, SoundEffects.BLOCK_WOOD_PLACE, SoundEffects.BLOCK_WOOD_HIT, SoundEffects.BLOCK_WOOD_FALL); ++ public static final SoundEffectType b = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_GRAVEL_BREAK, SoundEffects.BLOCK_GRAVEL_STEP, SoundEffects.BLOCK_GRAVEL_PLACE, SoundEffects.BLOCK_GRAVEL_HIT, SoundEffects.BLOCK_GRAVEL_FALL); ++ public static final SoundEffectType c = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_GRASS_BREAK, SoundEffects.BLOCK_GRASS_STEP, SoundEffects.BLOCK_GRASS_PLACE, SoundEffects.BLOCK_GRASS_HIT, SoundEffects.BLOCK_GRASS_FALL); ++ public static final SoundEffectType d = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_GRASS_BREAK, SoundEffects.BLOCK_GRASS_STEP, SoundEffects.BLOCK_LILY_PAD_PLACE, SoundEffects.BLOCK_GRASS_HIT, SoundEffects.BLOCK_GRASS_FALL); ++ public static final SoundEffectType e = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_STONE_BREAK, SoundEffects.BLOCK_STONE_STEP, SoundEffects.BLOCK_STONE_PLACE, SoundEffects.BLOCK_STONE_HIT, SoundEffects.BLOCK_STONE_FALL); ++ public static final SoundEffectType f = new SoundEffectType(1.0F, 1.5F, SoundEffects.BLOCK_METAL_BREAK, SoundEffects.BLOCK_METAL_STEP, SoundEffects.BLOCK_METAL_PLACE, SoundEffects.BLOCK_METAL_HIT, SoundEffects.BLOCK_METAL_FALL); ++ public static final SoundEffectType g = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_GLASS_BREAK, SoundEffects.BLOCK_GLASS_STEP, SoundEffects.BLOCK_GLASS_PLACE, SoundEffects.BLOCK_GLASS_HIT, SoundEffects.BLOCK_GLASS_FALL); ++ public static final SoundEffectType h = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_WOOL_BREAK, SoundEffects.BLOCK_WOOL_STEP, SoundEffects.BLOCK_WOOL_PLACE, SoundEffects.BLOCK_WOOL_HIT, SoundEffects.BLOCK_WOOL_FALL); ++ public static final SoundEffectType i = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_SAND_BREAK, SoundEffects.BLOCK_SAND_STEP, SoundEffects.BLOCK_SAND_PLACE, SoundEffects.BLOCK_SAND_HIT, SoundEffects.BLOCK_SAND_FALL); ++ public static final SoundEffectType j = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_SNOW_BREAK, SoundEffects.BLOCK_SNOW_STEP, SoundEffects.BLOCK_SNOW_PLACE, SoundEffects.BLOCK_SNOW_HIT, SoundEffects.BLOCK_SNOW_FALL); ++ public static final SoundEffectType k = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_LADDER_BREAK, SoundEffects.BLOCK_LADDER_STEP, SoundEffects.BLOCK_LADDER_PLACE, SoundEffects.BLOCK_LADDER_HIT, SoundEffects.BLOCK_LADDER_FALL); ++ public static final SoundEffectType l = new SoundEffectType(0.3F, 1.0F, SoundEffects.BLOCK_ANVIL_BREAK, SoundEffects.BLOCK_ANVIL_STEP, SoundEffects.BLOCK_ANVIL_PLACE, SoundEffects.BLOCK_ANVIL_HIT, SoundEffects.BLOCK_ANVIL_FALL); ++ public static final SoundEffectType m = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_SLIME_BLOCK_BREAK, SoundEffects.BLOCK_SLIME_BLOCK_STEP, SoundEffects.BLOCK_SLIME_BLOCK_PLACE, SoundEffects.BLOCK_SLIME_BLOCK_HIT, SoundEffects.BLOCK_SLIME_BLOCK_FALL); ++ public static final SoundEffectType n = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_HONEY_BLOCK_BREAK, SoundEffects.BLOCK_HONEY_BLOCK_STEP, SoundEffects.BLOCK_HONEY_BLOCK_PLACE, SoundEffects.BLOCK_HONEY_BLOCK_HIT, SoundEffects.BLOCK_HONEY_BLOCK_FALL); ++ public static final SoundEffectType o = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_WET_GRASS_BREAK, SoundEffects.BLOCK_WET_GRASS_STEP, SoundEffects.BLOCK_WET_GRASS_PLACE, SoundEffects.BLOCK_WET_GRASS_HIT, SoundEffects.BLOCK_WET_GRASS_FALL); ++ public static final SoundEffectType p = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_CORAL_BLOCK_BREAK, SoundEffects.BLOCK_CORAL_BLOCK_STEP, SoundEffects.BLOCK_CORAL_BLOCK_PLACE, SoundEffects.BLOCK_CORAL_BLOCK_HIT, SoundEffects.BLOCK_CORAL_BLOCK_FALL); ++ public static final SoundEffectType q = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_BAMBOO_BREAK, SoundEffects.BLOCK_BAMBOO_STEP, SoundEffects.BLOCK_BAMBOO_PLACE, SoundEffects.BLOCK_BAMBOO_HIT, SoundEffects.BLOCK_BAMBOO_FALL); ++ public static final SoundEffectType r = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_BAMBOO_SAPLING_BREAK, SoundEffects.BLOCK_BAMBOO_STEP, SoundEffects.BLOCK_BAMBOO_SAPLING_PLACE, SoundEffects.BLOCK_BAMBOO_SAPLING_HIT, SoundEffects.BLOCK_BAMBOO_FALL); ++ public static final SoundEffectType s = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_SCAFFOLDING_BREAK, SoundEffects.BLOCK_SCAFFOLDING_STEP, SoundEffects.BLOCK_SCAFFOLDING_PLACE, SoundEffects.BLOCK_SCAFFOLDING_HIT, SoundEffects.BLOCK_SCAFFOLDING_FALL); ++ public static final SoundEffectType t = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_SWEET_BERRY_BUSH_BREAK, SoundEffects.BLOCK_GRASS_STEP, SoundEffects.BLOCK_SWEET_BERRY_BUSH_PLACE, SoundEffects.BLOCK_GRASS_HIT, SoundEffects.BLOCK_GRASS_FALL); ++ public static final SoundEffectType u = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_CROP_BREAK, SoundEffects.BLOCK_GRASS_STEP, SoundEffects.ITEM_CROP_PLANT, SoundEffects.BLOCK_GRASS_HIT, SoundEffects.BLOCK_GRASS_FALL); ++ public static final SoundEffectType v = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_WOOD_BREAK, SoundEffects.BLOCK_WOOD_STEP, SoundEffects.ITEM_CROP_PLANT, SoundEffects.BLOCK_WOOD_HIT, SoundEffects.BLOCK_WOOD_FALL); ++ public static final SoundEffectType w = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_GRASS_BREAK, SoundEffects.BLOCK_VINE_STEP, SoundEffects.BLOCK_GRASS_PLACE, SoundEffects.BLOCK_GRASS_HIT, SoundEffects.BLOCK_GRASS_FALL); ++ public static final SoundEffectType x = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_NETHER_WART_BREAK, SoundEffects.BLOCK_STONE_STEP, SoundEffects.ITEM_NETHER_WART_PLANT, SoundEffects.BLOCK_STONE_HIT, SoundEffects.BLOCK_STONE_FALL); ++ public static final SoundEffectType y = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_LANTERN_BREAK, SoundEffects.BLOCK_LANTERN_STEP, SoundEffects.BLOCK_LANTERN_PLACE, SoundEffects.BLOCK_LANTERN_HIT, SoundEffects.BLOCK_LANTERN_FALL); ++ public static final SoundEffectType z = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_STEM_BREAK, SoundEffects.BLOCK_STEM_STEP, SoundEffects.BLOCK_STEM_PLACE, SoundEffects.BLOCK_STEM_HIT, SoundEffects.BLOCK_STEM_FALL); ++ public static final SoundEffectType A = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_NYLIUM_BREAK, SoundEffects.BLOCK_NYLIUM_STEP, SoundEffects.BLOCK_NYLIUM_PLACE, SoundEffects.BLOCK_NYLIUM_HIT, SoundEffects.BLOCK_NYLIUM_FALL); ++ public static final SoundEffectType B = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_FUNGUS_BREAK, SoundEffects.BLOCK_FUNGUS_STEP, SoundEffects.BLOCK_FUNGUS_PLACE, SoundEffects.BLOCK_FUNGUS_HIT, SoundEffects.BLOCK_FUNGUS_FALL); ++ public static final SoundEffectType C = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_ROOTS_BREAK, SoundEffects.BLOCK_ROOTS_STEP, SoundEffects.BLOCK_ROOTS_PLACE, SoundEffects.BLOCK_ROOTS_HIT, SoundEffects.BLOCK_ROOTS_FALL); ++ public static final SoundEffectType D = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_SHROOMLIGHT_BREAK, SoundEffects.BLOCK_SHROOMLIGHT_STEP, SoundEffects.BLOCK_SHROOMLIGHT_PLACE, SoundEffects.BLOCK_SHROOMLIGHT_HIT, SoundEffects.BLOCK_SHROOMLIGHT_FALL); ++ public static final SoundEffectType E = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_WEEPING_VINES_BREAK, SoundEffects.BLOCK_WEEPING_VINES_STEP, SoundEffects.BLOCK_WEEPING_VINES_PLACE, SoundEffects.BLOCK_WEEPING_VINES_HIT, SoundEffects.BLOCK_WEEPING_VINES_FALL); ++ public static final SoundEffectType F = new SoundEffectType(1.0F, 0.5F, SoundEffects.BLOCK_WEEPING_VINES_BREAK, SoundEffects.BLOCK_WEEPING_VINES_STEP, SoundEffects.BLOCK_WEEPING_VINES_PLACE, SoundEffects.BLOCK_WEEPING_VINES_HIT, SoundEffects.BLOCK_WEEPING_VINES_FALL); ++ public static final SoundEffectType G = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_SOUL_SAND_BREAK, SoundEffects.BLOCK_SOUL_SAND_STEP, SoundEffects.BLOCK_SOUL_SAND_PLACE, SoundEffects.BLOCK_SOUL_SAND_HIT, SoundEffects.BLOCK_SOUL_SAND_FALL); ++ public static final SoundEffectType H = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_SOUL_SOIL_BREAK, SoundEffects.BLOCK_SOUL_SOIL_STEP, SoundEffects.BLOCK_SOUL_SOIL_PLACE, SoundEffects.BLOCK_SOUL_SOIL_HIT, SoundEffects.BLOCK_SOUL_SOIL_FALL); ++ public static final SoundEffectType I = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_BASALT_BREAK, SoundEffects.BLOCK_BASALT_STEP, SoundEffects.BLOCK_BASALT_PLACE, SoundEffects.BLOCK_BASALT_HIT, SoundEffects.BLOCK_BASALT_FALL); ++ public static final SoundEffectType J = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_WART_BLOCK_BREAK, SoundEffects.BLOCK_WART_BLOCK_STEP, SoundEffects.BLOCK_WART_BLOCK_PLACE, SoundEffects.BLOCK_WART_BLOCK_HIT, SoundEffects.BLOCK_WART_BLOCK_FALL); ++ public static final SoundEffectType K = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_NETHERRACK_BREAK, SoundEffects.BLOCK_NETHERRACK_STEP, SoundEffects.BLOCK_NETHERRACK_PLACE, SoundEffects.BLOCK_NETHERRACK_HIT, SoundEffects.BLOCK_NETHERRACK_FALL); ++ public static final SoundEffectType L = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_NETHER_BRICKS_BREAK, SoundEffects.BLOCK_NETHER_BRICKS_STEP, SoundEffects.BLOCK_NETHER_BRICKS_PLACE, SoundEffects.BLOCK_NETHER_BRICKS_HIT, SoundEffects.BLOCK_NETHER_BRICKS_FALL); ++ public static final SoundEffectType M = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_NETHER_SPROUTS_BREAK, SoundEffects.BLOCK_NETHER_SPROUTS_STEP, SoundEffects.BLOCK_NETHER_SPROUTS_PLACE, SoundEffects.BLOCK_NETHER_SPROUTS_HIT, SoundEffects.BLOCK_NETHER_SPROUTS_FALL); ++ public static final SoundEffectType N = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_NETHER_ORE_BREAK, SoundEffects.BLOCK_NETHER_ORE_STEP, SoundEffects.BLOCK_NETHER_ORE_PLACE, SoundEffects.BLOCK_NETHER_ORE_HIT, SoundEffects.BLOCK_NETHER_ORE_FALL); ++ public static final SoundEffectType O = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_BONE_BLOCK_BREAK, SoundEffects.BLOCK_BONE_BLOCK_STEP, SoundEffects.BLOCK_BONE_BLOCK_PLACE, SoundEffects.BLOCK_BONE_BLOCK_HIT, SoundEffects.BLOCK_BONE_BLOCK_FALL); ++ public static final SoundEffectType P = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_NETHERITE_BLOCK_BREAK, SoundEffects.BLOCK_NETHERITE_BLOCK_STEP, SoundEffects.BLOCK_NETHERITE_BLOCK_PLACE, SoundEffects.BLOCK_NETHERITE_BLOCK_HIT, SoundEffects.BLOCK_NETHERITE_BLOCK_FALL); ++ public static final SoundEffectType Q = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_ANCIENT_DEBRIS_BREAK, SoundEffects.BLOCK_ANCIENT_DEBRIS_STEP, SoundEffects.BLOCK_ANCIENT_DEBRIS_PLACE, SoundEffects.BLOCK_ANCIENT_DEBRIS_HIT, SoundEffects.BLOCK_ANCIENT_DEBRIS_FALL); ++ public static final SoundEffectType R = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_LODESTONE_BREAK, SoundEffects.BLOCK_LODESTONE_STEP, SoundEffects.BLOCK_LODESTONE_PLACE, SoundEffects.BLOCK_LODESTONE_HIT, SoundEffects.BLOCK_LODESTONE_FALL); ++ public static final SoundEffectType S = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_CHAIN_BREAK, SoundEffects.BLOCK_CHAIN_STEP, SoundEffects.BLOCK_CHAIN_PLACE, SoundEffects.BLOCK_CHAIN_HIT, SoundEffects.BLOCK_CHAIN_FALL); ++ public static final SoundEffectType T = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_NETHER_GOLD_ORE_BREAK, SoundEffects.BLOCK_NETHER_GOLD_ORE_STEP, SoundEffects.BLOCK_NETHER_GOLD_ORE_PLACE, SoundEffects.BLOCK_NETHER_GOLD_ORE_HIT, SoundEffects.BLOCK_NETHER_GOLD_ORE_FALL); ++ public static final SoundEffectType U = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_GILDED_BLACKSTONE_BREAK, SoundEffects.BLOCK_GILDED_BLACKSTONE_STEP, SoundEffects.BLOCK_GILDED_BLACKSTONE_PLACE, SoundEffects.BLOCK_GILDED_BLACKSTONE_HIT, SoundEffects.BLOCK_GILDED_BLACKSTONE_FALL); ++ public final float volume; ++ public final float pitch; ++ public final SoundEffect breakSound; ++ private final SoundEffect stepSound; ++ private final SoundEffect placeSound; ++ public final SoundEffect hitSound; ++ private final SoundEffect fallSound; ++ ++ public SoundEffectType(float f, float f1, SoundEffect soundeffect, SoundEffect soundeffect1, SoundEffect soundeffect2, SoundEffect soundeffect3, SoundEffect soundeffect4) { ++ this.volume = f; ++ this.pitch = f1; ++ this.breakSound = soundeffect; ++ this.stepSound = soundeffect1; ++ this.placeSound = soundeffect2; ++ this.hitSound = soundeffect3; ++ this.fallSound = soundeffect4; ++ } ++ ++ public float getVolume() { ++ return this.volume; ++ } ++ ++ public float getPitch() { ++ return this.pitch; ++ } ++ ++ public SoundEffect getStepSound() { ++ return this.stepSound; ++ } ++ ++ public SoundEffect getPlaceSound() { ++ return this.placeSound; ++ } ++ ++ public SoundEffect getFallSound() { ++ return this.fallSound; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnumBannerPatternType.java b/src/main/java/net/minecraft/world/level/block/entity/EnumBannerPatternType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..988e52c675dbb5ef368c8dbb5fb6d4229eb30174 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/entity/EnumBannerPatternType.java +@@ -0,0 +1,67 @@ ++package net.minecraft.world.level.block.entity; ++ ++import com.google.common.collect.Lists; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.List; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.world.item.EnumColor; ++import org.apache.commons.lang3.tuple.Pair; ++ ++public enum EnumBannerPatternType { ++ ++ BASE("base", "b", false), SQUARE_BOTTOM_LEFT("square_bottom_left", "bl"), SQUARE_BOTTOM_RIGHT("square_bottom_right", "br"), SQUARE_TOP_LEFT("square_top_left", "tl"), SQUARE_TOP_RIGHT("square_top_right", "tr"), STRIPE_BOTTOM("stripe_bottom", "bs"), STRIPE_TOP("stripe_top", "ts"), STRIPE_LEFT("stripe_left", "ls"), STRIPE_RIGHT("stripe_right", "rs"), STRIPE_CENTER("stripe_center", "cs"), STRIPE_MIDDLE("stripe_middle", "ms"), STRIPE_DOWNRIGHT("stripe_downright", "drs"), STRIPE_DOWNLEFT("stripe_downleft", "dls"), STRIPE_SMALL("small_stripes", "ss"), CROSS("cross", "cr"), STRAIGHT_CROSS("straight_cross", "sc"), TRIANGLE_BOTTOM("triangle_bottom", "bt"), TRIANGLE_TOP("triangle_top", "tt"), TRIANGLES_BOTTOM("triangles_bottom", "bts"), TRIANGLES_TOP("triangles_top", "tts"), DIAGONAL_LEFT("diagonal_left", "ld"), DIAGONAL_RIGHT("diagonal_up_right", "rd"), DIAGONAL_LEFT_MIRROR("diagonal_up_left", "lud"), DIAGONAL_RIGHT_MIRROR("diagonal_right", "rud"), CIRCLE_MIDDLE("circle", "mc"), RHOMBUS_MIDDLE("rhombus", "mr"), HALF_VERTICAL("half_vertical", "vh"), HALF_HORIZONTAL("half_horizontal", "hh"), HALF_VERTICAL_MIRROR("half_vertical_right", "vhr"), HALF_HORIZONTAL_MIRROR("half_horizontal_bottom", "hhb"), BORDER("border", "bo"), CURLY_BORDER("curly_border", "cbo"), GRADIENT("gradient", "gra"), GRADIENT_UP("gradient_up", "gru"), BRICKS("bricks", "bri"), GLOBE("globe", "glb", true), CREEPER("creeper", "cre", true), SKULL("skull", "sku", true), FLOWER("flower", "flo", true), MOJANG("mojang", "moj", true), PIGLIN("piglin", "pig", true); ++ ++ private static final EnumBannerPatternType[] S = values(); ++ public static final int P = EnumBannerPatternType.S.length; ++ public static final int Q = (int) Arrays.stream(EnumBannerPatternType.S).filter((enumbannerpatterntype) -> { ++ return enumbannerpatterntype.T; ++ }).count(); ++ public static final int R = EnumBannerPatternType.P - EnumBannerPatternType.Q - 1; ++ private final boolean T; ++ private final String U; ++ private final String V; ++ ++ private EnumBannerPatternType(String s, String s1) { ++ this(s, s1, false); ++ } ++ ++ private EnumBannerPatternType(String s, String s1, boolean flag) { ++ this.U = s; ++ this.V = s1; ++ this.T = flag; ++ } ++ ++ public String b() { ++ return this.V; ++ } ++ ++ public static class a { ++ ++ private final List> a = Lists.newArrayList(); ++ ++ public a() {} ++ ++ public EnumBannerPatternType.a a(EnumBannerPatternType enumbannerpatterntype, EnumColor enumcolor) { ++ this.a.add(Pair.of(enumbannerpatterntype, enumcolor)); ++ return this; ++ } ++ ++ public NBTTagList a() { ++ NBTTagList nbttaglist = new NBTTagList(); ++ Iterator iterator = this.a.iterator(); ++ ++ while (iterator.hasNext()) { ++ Pair pair = (Pair) iterator.next(); ++ NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ ++ nbttagcompound.setString("Pattern", ((EnumBannerPatternType) pair.getLeft()).V); ++ nbttagcompound.setInt("Color", ((EnumColor) pair.getRight()).getColorIndex()); ++ nbttaglist.add(nbttagcompound); ++ } ++ ++ return nbttaglist; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/entity/IHopper.java b/src/main/java/net/minecraft/world/level/block/entity/IHopper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d0943ae1f372784716195666212ff83e6ee4873e +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/entity/IHopper.java +@@ -0,0 +1,28 @@ ++package net.minecraft.world.level.block.entity; ++ ++import javax.annotation.Nullable; ++import net.minecraft.world.IInventory; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapes; ++ ++public interface IHopper extends IInventory { ++ ++ VoxelShape a = Block.a(2.0D, 11.0D, 2.0D, 14.0D, 16.0D, 14.0D); ++ VoxelShape b = Block.a(0.0D, 16.0D, 0.0D, 16.0D, 32.0D, 16.0D); ++ VoxelShape c = VoxelShapes.a(IHopper.a, IHopper.b); ++ ++ default VoxelShape aa_() { ++ return IHopper.c; ++ } ++ ++ @Nullable ++ World getWorld(); ++ ++ double x(); ++ ++ double z(); ++ ++ double A(); ++} +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e97d229da3cf7631555f418a73bc74255494cc01 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java +@@ -0,0 +1,179 @@ ++package net.minecraft.world.level.block.entity; ++ ++import java.util.Iterator; ++import java.util.List; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.core.IPosition; ++import net.minecraft.core.particles.Particles; ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsEntity; ++import net.minecraft.util.ColorUtil; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.effect.MobEffect; ++import net.minecraft.world.effect.MobEffects; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.level.World; ++import net.minecraft.world.phys.AxisAlignedBB; ++import org.apache.commons.lang3.mutable.MutableInt; ++ ++public class TileEntityBell extends TileEntity implements ITickable { ++ ++ private long g; ++ public int a; ++ public boolean b; ++ public EnumDirection c; ++ private List h; ++ private boolean i; ++ private int j; ++ ++ public TileEntityBell() { ++ super(TileEntityTypes.BELL); ++ } ++ ++ @Override ++ public boolean setProperty(int i, int j) { ++ if (i == 1) { ++ this.f(); ++ this.j = 0; ++ this.c = EnumDirection.fromType1(j); ++ this.a = 0; ++ this.b = true; ++ return true; ++ } else { ++ return super.setProperty(i, j); ++ } ++ } ++ ++ @Override ++ public void tick() { ++ if (this.b) { ++ ++this.a; ++ } ++ ++ if (this.a >= 50) { ++ this.b = false; ++ this.a = 0; ++ } ++ ++ if (this.a >= 5 && this.j == 0 && this.h()) { ++ this.i = true; ++ this.d(); ++ } ++ ++ if (this.i) { ++ if (this.j < 40) { ++ ++this.j; ++ } else { ++ this.a(this.world); ++ this.b(this.world); ++ this.i = false; ++ } ++ } ++ ++ } ++ ++ private void d() { ++ this.world.playSound((EntityHuman) null, this.getPosition(), SoundEffects.BLOCK_BELL_RESONATE, SoundCategory.BLOCKS, 1.0F, 1.0F); ++ } ++ ++ public void a(EnumDirection enumdirection) { ++ BlockPosition blockposition = this.getPosition(); ++ ++ this.c = enumdirection; ++ if (this.b) { ++ this.a = 0; ++ } else { ++ this.b = true; ++ } ++ ++ this.world.playBlockAction(blockposition, this.getBlock().getBlock(), 1, enumdirection.c()); ++ } ++ ++ private void f() { ++ BlockPosition blockposition = this.getPosition(); ++ ++ if (this.world.getTime() > this.g + 60L || this.h == null) { ++ this.g = this.world.getTime(); ++ AxisAlignedBB axisalignedbb = (new AxisAlignedBB(blockposition)).g(48.0D); ++ ++ this.h = this.world.a(EntityLiving.class, axisalignedbb); ++ } ++ ++ if (!this.world.isClientSide) { ++ Iterator iterator = this.h.iterator(); ++ ++ while (iterator.hasNext()) { ++ EntityLiving entityliving = (EntityLiving) iterator.next(); ++ ++ if (entityliving.isAlive() && !entityliving.dead && blockposition.a((IPosition) entityliving.getPositionVector(), 32.0D)) { ++ entityliving.getBehaviorController().setMemory(MemoryModuleType.HEARD_BELL_TIME, (Object) this.world.getTime()); ++ } ++ } ++ } ++ ++ } ++ ++ private boolean h() { ++ BlockPosition blockposition = this.getPosition(); ++ Iterator iterator = this.h.iterator(); ++ ++ EntityLiving entityliving; ++ ++ do { ++ if (!iterator.hasNext()) { ++ return false; ++ } ++ ++ entityliving = (EntityLiving) iterator.next(); ++ } while (!entityliving.isAlive() || entityliving.dead || !blockposition.a((IPosition) entityliving.getPositionVector(), 32.0D) || !entityliving.getEntityType().a((Tag) TagsEntity.RADIERS)); ++ ++ return true; ++ } ++ ++ private void a(World world) { ++ if (!world.isClientSide) { ++ this.h.stream().filter(this::a).forEach(this::b); ++ } ++ } ++ ++ private void b(World world) { ++ if (world.isClientSide) { ++ BlockPosition blockposition = this.getPosition(); ++ MutableInt mutableint = new MutableInt(16700985); ++ int i = (int) this.h.stream().filter((entityliving) -> { ++ return blockposition.a((IPosition) entityliving.getPositionVector(), 48.0D); ++ }).count(); ++ ++ this.h.stream().filter(this::a).forEach((entityliving) -> { ++ float f = 1.0F; ++ float f1 = MathHelper.sqrt((entityliving.locX() - (double) blockposition.getX()) * (entityliving.locX() - (double) blockposition.getX()) + (entityliving.locZ() - (double) blockposition.getZ()) * (entityliving.locZ() - (double) blockposition.getZ())); ++ double d0 = (double) ((float) blockposition.getX() + 0.5F) + (double) (1.0F / f1) * (entityliving.locX() - (double) blockposition.getX()); ++ double d1 = (double) ((float) blockposition.getZ() + 0.5F) + (double) (1.0F / f1) * (entityliving.locZ() - (double) blockposition.getZ()); ++ int j = MathHelper.clamp((i - 21) / -2, 3, 15); ++ ++ for (int k = 0; k < j; ++k) { ++ int l = mutableint.addAndGet(5); ++ double d2 = (double) ColorUtil.a.b(l) / 255.0D; ++ double d3 = (double) ColorUtil.a.c(l) / 255.0D; ++ double d4 = (double) ColorUtil.a.d(l) / 255.0D; ++ ++ world.addParticle(Particles.ENTITY_EFFECT, d0, (double) ((float) blockposition.getY() + 0.5F), d1, d2, d3, d4); ++ } ++ ++ }); ++ } ++ } ++ ++ private boolean a(EntityLiving entityliving) { ++ return entityliving.isAlive() && !entityliving.dead && this.getPosition().a((IPosition) entityliving.getPositionVector(), 48.0D) && entityliving.getEntityType().a((Tag) TagsEntity.RADIERS); ++ } ++ ++ private void b(EntityLiving entityliving) { ++ entityliving.addEffect(new MobEffect(MobEffects.GLOWING, 60)); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnderChest.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnderChest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..930f1bd091d9754f7ca5d9e36cdf49b2be03eb23 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnderChest.java +@@ -0,0 +1,97 @@ ++package net.minecraft.world.level.block.entity; ++ ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.sounds.SoundEffects; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.level.block.Blocks; ++ ++public class TileEntityEnderChest extends TileEntity implements ITickable { ++ ++ public float a; ++ public float b; ++ public int c; ++ private int g; ++ ++ public TileEntityEnderChest() { ++ super(TileEntityTypes.ENDER_CHEST); ++ } ++ ++ @Override ++ public void tick() { ++ if (++this.g % 20 * 4 == 0) { ++ this.world.playBlockAction(this.position, Blocks.ENDER_CHEST, 1, this.c); ++ } ++ ++ this.b = this.a; ++ int i = this.position.getX(); ++ int j = this.position.getY(); ++ int k = this.position.getZ(); ++ float f = 0.1F; ++ double d0; ++ ++ if (this.c > 0 && this.a == 0.0F) { ++ double d1 = (double) i + 0.5D; ++ ++ d0 = (double) k + 0.5D; ++ this.world.playSound((EntityHuman) null, d1, (double) j + 0.5D, d0, SoundEffects.BLOCK_ENDER_CHEST_OPEN, SoundCategory.BLOCKS, 0.5F, this.world.random.nextFloat() * 0.1F + 0.9F); ++ } ++ ++ if (this.c == 0 && this.a > 0.0F || this.c > 0 && this.a < 1.0F) { ++ float f1 = this.a; ++ ++ if (this.c > 0) { ++ this.a += 0.1F; ++ } else { ++ this.a -= 0.1F; ++ } ++ ++ if (this.a > 1.0F) { ++ this.a = 1.0F; ++ } ++ ++ float f2 = 0.5F; ++ ++ if (this.a < 0.5F && f1 >= 0.5F) { ++ d0 = (double) i + 0.5D; ++ double d2 = (double) k + 0.5D; ++ ++ this.world.playSound((EntityHuman) null, d0, (double) j + 0.5D, d2, SoundEffects.BLOCK_ENDER_CHEST_CLOSE, SoundCategory.BLOCKS, 0.5F, this.world.random.nextFloat() * 0.1F + 0.9F); ++ } ++ ++ if (this.a < 0.0F) { ++ this.a = 0.0F; ++ } ++ } ++ ++ } ++ ++ @Override ++ public boolean setProperty(int i, int j) { ++ if (i == 1) { ++ this.c = j; ++ return true; ++ } else { ++ return super.setProperty(i, j); ++ } ++ } ++ ++ @Override ++ public void al_() { ++ this.invalidateBlockCache(); ++ super.al_(); ++ } ++ ++ public void d() { ++ ++this.c; ++ this.world.playBlockAction(this.position, Blocks.ENDER_CHEST, 1, this.c); ++ } ++ ++ public void f() { ++ --this.c; ++ this.world.playBlockAction(this.position, Blocks.ENDER_CHEST, 1, this.c); ++ } ++ ++ public boolean a(EntityHuman entityhuman) { ++ return this.world.getTileEntity(this.position) != this ? false : entityhuman.h((double) this.position.getX() + 0.5D, (double) this.position.getY() + 0.5D, (double) this.position.getZ() + 0.5D) <= 64.0D; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityLootable.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityLootable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..62e6833a90d7adae3c7df33e3bc73b4288e0370b +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityLootable.java +@@ -0,0 +1,162 @@ ++package net.minecraft.world.level.block.entity; ++ ++import java.util.Random; ++import javax.annotation.Nullable; ++import net.minecraft.advancements.CriterionTriggers; ++import net.minecraft.core.BaseBlockPosition; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.NonNullList; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.ContainerUtil; ++import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.player.PlayerInventory; ++import net.minecraft.world.inventory.Container; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.storage.loot.LootTable; ++import net.minecraft.world.level.storage.loot.LootTableInfo; ++import net.minecraft.world.level.storage.loot.parameters.LootContextParameterSets; ++import net.minecraft.world.level.storage.loot.parameters.LootContextParameters; ++import net.minecraft.world.phys.Vec3D; ++ ++public abstract class TileEntityLootable extends TileEntityContainer { ++ ++ @Nullable ++ public MinecraftKey lootTable; ++ public long lootTableSeed; ++ ++ protected TileEntityLootable(TileEntityTypes tileentitytypes) { ++ super(tileentitytypes); ++ } ++ ++ public static void a(IBlockAccess iblockaccess, Random random, BlockPosition blockposition, MinecraftKey minecraftkey) { ++ TileEntity tileentity = iblockaccess.getTileEntity(blockposition); ++ ++ if (tileentity instanceof TileEntityLootable) { ++ ((TileEntityLootable) tileentity).setLootTable(minecraftkey, random.nextLong()); ++ } ++ ++ } ++ ++ protected boolean b(NBTTagCompound nbttagcompound) { ++ if (nbttagcompound.hasKeyOfType("LootTable", 8)) { ++ this.lootTable = new MinecraftKey(nbttagcompound.getString("LootTable")); ++ this.lootTableSeed = nbttagcompound.getLong("LootTableSeed"); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ protected boolean c(NBTTagCompound nbttagcompound) { ++ if (this.lootTable == null) { ++ return false; ++ } else { ++ nbttagcompound.setString("LootTable", this.lootTable.toString()); ++ if (this.lootTableSeed != 0L) { ++ nbttagcompound.setLong("LootTableSeed", this.lootTableSeed); ++ } ++ ++ return true; ++ } ++ } ++ ++ public void d(@Nullable EntityHuman entityhuman) { ++ if (this.lootTable != null && this.world.getMinecraftServer() != null) { ++ LootTable loottable = this.world.getMinecraftServer().getLootTableRegistry().getLootTable(this.lootTable); ++ ++ if (entityhuman instanceof EntityPlayer) { ++ CriterionTriggers.N.a((EntityPlayer) entityhuman, this.lootTable); ++ } ++ ++ this.lootTable = null; ++ LootTableInfo.Builder loottableinfo_builder = (new LootTableInfo.Builder((WorldServer) this.world)).set(LootContextParameters.ORIGIN, Vec3D.a((BaseBlockPosition) this.position)).a(this.lootTableSeed); ++ ++ if (entityhuman != null) { ++ loottableinfo_builder.a(entityhuman.eU()).set(LootContextParameters.THIS_ENTITY, entityhuman); ++ } ++ ++ loottable.fillInventory(this, loottableinfo_builder.build(LootContextParameterSets.CHEST)); ++ } ++ ++ } ++ ++ public void setLootTable(MinecraftKey minecraftkey, long i) { ++ this.lootTable = minecraftkey; ++ this.lootTableSeed = i; ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ this.d((EntityHuman) null); ++ return this.f().stream().allMatch(ItemStack::isEmpty); ++ } ++ ++ @Override ++ public ItemStack getItem(int i) { ++ this.d((EntityHuman) null); ++ return (ItemStack) this.f().get(i); ++ } ++ ++ @Override ++ public ItemStack splitStack(int i, int j) { ++ this.d((EntityHuman) null); ++ ItemStack itemstack = ContainerUtil.a(this.f(), i, j); ++ ++ if (!itemstack.isEmpty()) { ++ this.update(); ++ } ++ ++ return itemstack; ++ } ++ ++ @Override ++ public ItemStack splitWithoutUpdate(int i) { ++ this.d((EntityHuman) null); ++ return ContainerUtil.a(this.f(), i); ++ } ++ ++ @Override ++ public void setItem(int i, ItemStack itemstack) { ++ this.d((EntityHuman) null); ++ this.f().set(i, itemstack); ++ if (itemstack.getCount() > this.getMaxStackSize()) { ++ itemstack.setCount(this.getMaxStackSize()); ++ } ++ ++ this.update(); ++ } ++ ++ @Override ++ public boolean a(EntityHuman entityhuman) { ++ return this.world.getTileEntity(this.position) != this ? false : entityhuman.h((double) this.position.getX() + 0.5D, (double) this.position.getY() + 0.5D, (double) this.position.getZ() + 0.5D) <= 64.0D; ++ } ++ ++ @Override ++ public void clear() { ++ this.f().clear(); ++ } ++ ++ protected abstract NonNullList f(); ++ ++ protected abstract void a(NonNullList nonnulllist); ++ ++ @Override ++ public boolean e(EntityHuman entityhuman) { ++ return super.e(entityhuman) && (this.lootTable == null || !entityhuman.isSpectator()); ++ } ++ ++ @Nullable ++ @Override ++ public Container createMenu(int i, PlayerInventory playerinventory, EntityHuman entityhuman) { ++ if (this.e(entityhuman)) { ++ this.d(playerinventory.player); ++ return this.createContainer(i, playerinventory); ++ } else { ++ return null; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java b/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a549195e67236c0146861b896fb9e4907073af58 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java +@@ -0,0 +1,364 @@ ++package net.minecraft.world.level.block.piston; ++ ++import java.util.Iterator; ++import java.util.List; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.nbt.GameProfileSerializer; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EnumMoveType; ++import net.minecraft.world.level.GeneratorAccess; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.entity.ITickable; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.entity.TileEntityTypes; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.block.state.properties.BlockProperties; ++import net.minecraft.world.level.block.state.properties.BlockPropertyPistonType; ++import net.minecraft.world.level.material.EnumPistonReaction; ++import net.minecraft.world.phys.AxisAlignedBB; ++import net.minecraft.world.phys.Vec3D; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapes; ++ ++public class TileEntityPiston extends TileEntity implements ITickable { ++ ++ private IBlockData a; ++ private EnumDirection b; ++ private boolean c; ++ private boolean g; ++ private static final ThreadLocal h = ThreadLocal.withInitial(() -> { ++ return null; ++ }); ++ private float i; ++ private float j; ++ private long k; ++ private int l; ++ ++ public TileEntityPiston() { ++ super(TileEntityTypes.PISTON); ++ } ++ ++ public TileEntityPiston(IBlockData iblockdata, EnumDirection enumdirection, boolean flag, boolean flag1) { ++ this(); ++ this.a = iblockdata; ++ this.b = enumdirection; ++ this.c = flag; ++ this.g = flag1; ++ } ++ ++ @Override ++ public NBTTagCompound b() { ++ return this.save(new NBTTagCompound()); ++ } ++ ++ public boolean d() { ++ return this.c; ++ } ++ ++ public EnumDirection f() { ++ return this.b; ++ } ++ ++ public boolean h() { ++ return this.g; ++ } ++ ++ public float a(float f) { ++ if (f > 1.0F) { ++ f = 1.0F; ++ } ++ ++ return MathHelper.g(f, this.j, this.i); ++ } ++ ++ private float e(float f) { ++ return this.c ? f - 1.0F : 1.0F - f; ++ } ++ ++ private IBlockData x() { ++ return !this.d() && this.h() && this.a.getBlock() instanceof BlockPiston ? (IBlockData) ((IBlockData) ((IBlockData) Blocks.PISTON_HEAD.getBlockData().set(BlockPistonExtension.SHORT, this.i > 0.25F)).set(BlockPistonExtension.TYPE, this.a.a(Blocks.STICKY_PISTON) ? BlockPropertyPistonType.STICKY : BlockPropertyPistonType.DEFAULT)).set(BlockPistonExtension.FACING, this.a.get(BlockPiston.FACING)) : this.a; ++ } ++ ++ private void f(float f) { ++ EnumDirection enumdirection = this.j(); ++ double d0 = (double) (f - this.i); ++ VoxelShape voxelshape = this.x().getCollisionShape(this.world, this.getPosition()); ++ ++ if (!voxelshape.isEmpty()) { ++ AxisAlignedBB axisalignedbb = this.a(voxelshape.getBoundingBox()); ++ List list = this.world.getEntities((Entity) null, PistonUtil.a(axisalignedbb, enumdirection, d0).b(axisalignedbb)); ++ ++ if (!list.isEmpty()) { ++ List list1 = voxelshape.d(); ++ boolean flag = this.a.a(Blocks.SLIME_BLOCK); ++ Iterator iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ Entity entity = (Entity) iterator.next(); ++ ++ if (entity.getPushReaction() != EnumPistonReaction.IGNORE) { ++ if (flag) { ++ if (entity instanceof EntityPlayer) { ++ continue; ++ } ++ ++ Vec3D vec3d = entity.getMot(); ++ double d1 = vec3d.x; ++ double d2 = vec3d.y; ++ double d3 = vec3d.z; ++ ++ switch (enumdirection.n()) { ++ case X: ++ d1 = (double) enumdirection.getAdjacentX(); ++ break; ++ case Y: ++ d2 = (double) enumdirection.getAdjacentY(); ++ break; ++ case Z: ++ d3 = (double) enumdirection.getAdjacentZ(); ++ } ++ ++ entity.setMot(d1, d2, d3); ++ } ++ ++ double d4 = 0.0D; ++ Iterator iterator1 = list1.iterator(); ++ ++ while (iterator1.hasNext()) { ++ AxisAlignedBB axisalignedbb1 = (AxisAlignedBB) iterator1.next(); ++ AxisAlignedBB axisalignedbb2 = PistonUtil.a(this.a(axisalignedbb1), enumdirection, d0); ++ AxisAlignedBB axisalignedbb3 = entity.getBoundingBox(); ++ ++ if (axisalignedbb2.c(axisalignedbb3)) { ++ d4 = Math.max(d4, a(axisalignedbb2, enumdirection, axisalignedbb3)); ++ if (d4 >= d0) { ++ break; ++ } ++ } ++ } ++ ++ if (d4 > 0.0D) { ++ d4 = Math.min(d4, d0) + 0.01D; ++ a(enumdirection, entity, d4, enumdirection); ++ if (!this.c && this.g) { ++ this.a(entity, enumdirection, d0); ++ } ++ } ++ } ++ } ++ ++ } ++ } ++ } ++ ++ private static void a(EnumDirection enumdirection, Entity entity, double d0, EnumDirection enumdirection1) { ++ TileEntityPiston.h.set(enumdirection); ++ entity.move(EnumMoveType.PISTON, new Vec3D(d0 * (double) enumdirection1.getAdjacentX(), d0 * (double) enumdirection1.getAdjacentY(), d0 * (double) enumdirection1.getAdjacentZ())); ++ TileEntityPiston.h.set((Object) null); ++ } ++ ++ private void g(float f) { ++ if (this.y()) { ++ EnumDirection enumdirection = this.j(); ++ ++ if (enumdirection.n().d()) { ++ double d0 = this.a.getCollisionShape(this.world, this.position).c(EnumDirection.EnumAxis.Y); ++ AxisAlignedBB axisalignedbb = this.a(new AxisAlignedBB(0.0D, d0, 0.0D, 1.0D, 1.5000000999999998D, 1.0D)); ++ double d1 = (double) (f - this.i); ++ List list = this.world.getEntities((Entity) null, axisalignedbb, (entity) -> { ++ return a(axisalignedbb, entity); ++ }); ++ Iterator iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ Entity entity = (Entity) iterator.next(); ++ ++ a(enumdirection, entity, d1, enumdirection); ++ } ++ ++ } ++ } ++ } ++ ++ private static boolean a(AxisAlignedBB axisalignedbb, Entity entity) { ++ return entity.getPushReaction() == EnumPistonReaction.NORMAL && entity.isOnGround() && entity.locX() >= axisalignedbb.minX && entity.locX() <= axisalignedbb.maxX && entity.locZ() >= axisalignedbb.minZ && entity.locZ() <= axisalignedbb.maxZ; ++ } ++ ++ private boolean y() { ++ return this.a.a(Blocks.HONEY_BLOCK); ++ } ++ ++ public EnumDirection j() { ++ return this.c ? this.b : this.b.opposite(); ++ } ++ ++ private static double a(AxisAlignedBB axisalignedbb, EnumDirection enumdirection, AxisAlignedBB axisalignedbb1) { ++ switch (enumdirection) { ++ case EAST: ++ return axisalignedbb.maxX - axisalignedbb1.minX; ++ case WEST: ++ return axisalignedbb1.maxX - axisalignedbb.minX; ++ case UP: ++ default: ++ return axisalignedbb.maxY - axisalignedbb1.minY; ++ case DOWN: ++ return axisalignedbb1.maxY - axisalignedbb.minY; ++ case SOUTH: ++ return axisalignedbb.maxZ - axisalignedbb1.minZ; ++ case NORTH: ++ return axisalignedbb1.maxZ - axisalignedbb.minZ; ++ } ++ } ++ ++ private AxisAlignedBB a(AxisAlignedBB axisalignedbb) { ++ double d0 = (double) this.e(this.i); ++ ++ return axisalignedbb.d((double) this.position.getX() + d0 * (double) this.b.getAdjacentX(), (double) this.position.getY() + d0 * (double) this.b.getAdjacentY(), (double) this.position.getZ() + d0 * (double) this.b.getAdjacentZ()); ++ } ++ ++ private void a(Entity entity, EnumDirection enumdirection, double d0) { ++ AxisAlignedBB axisalignedbb = entity.getBoundingBox(); ++ AxisAlignedBB axisalignedbb1 = VoxelShapes.b().getBoundingBox().a(this.position); ++ ++ if (axisalignedbb.c(axisalignedbb1)) { ++ EnumDirection enumdirection1 = enumdirection.opposite(); ++ double d1 = a(axisalignedbb1, enumdirection1, axisalignedbb) + 0.01D; ++ double d2 = a(axisalignedbb1, enumdirection1, axisalignedbb.a(axisalignedbb1)) + 0.01D; ++ ++ if (Math.abs(d1 - d2) < 0.01D) { ++ d1 = Math.min(d1, d0) + 0.01D; ++ a(enumdirection, entity, d1, enumdirection1); ++ } ++ } ++ ++ } ++ ++ public IBlockData k() { ++ return this.a; ++ } ++ ++ public void l() { ++ if (this.world != null && (this.j < 1.0F || this.world.isClientSide)) { ++ this.i = 1.0F; ++ this.j = this.i; ++ this.world.removeTileEntity(this.position); ++ this.al_(); ++ if (this.world.getType(this.position).a(Blocks.MOVING_PISTON)) { ++ IBlockData iblockdata; ++ ++ if (this.g) { ++ iblockdata = Blocks.AIR.getBlockData(); ++ } else { ++ iblockdata = Block.b(this.a, (GeneratorAccess) this.world, this.position); ++ } ++ ++ this.world.setTypeAndData(this.position, iblockdata, 3); ++ this.world.a(this.position, iblockdata.getBlock(), this.position); ++ } ++ } ++ ++ } ++ ++ @Override ++ public void tick() { ++ this.k = this.world.getTime(); ++ this.j = this.i; ++ if (this.j >= 1.0F) { ++ if (this.world.isClientSide && this.l < 5) { ++ ++this.l; ++ } else { ++ this.world.removeTileEntity(this.position); ++ this.al_(); ++ if (this.a != null && this.world.getType(this.position).a(Blocks.MOVING_PISTON)) { ++ IBlockData iblockdata = Block.b(this.a, (GeneratorAccess) this.world, this.position); ++ ++ if (iblockdata.isAir()) { ++ this.world.setTypeAndData(this.position, this.a, 84); ++ Block.a(this.a, iblockdata, this.world, this.position, 3); ++ } else { ++ if (iblockdata.b(BlockProperties.C) && (Boolean) iblockdata.get(BlockProperties.C)) { ++ iblockdata = (IBlockData) iblockdata.set(BlockProperties.C, false); ++ } ++ ++ this.world.setTypeAndData(this.position, iblockdata, 67); ++ this.world.a(this.position, iblockdata.getBlock(), this.position); ++ } ++ } ++ ++ } ++ } else { ++ float f = this.i + 0.5F; ++ ++ this.f(f); ++ this.g(f); ++ this.i = f; ++ if (this.i >= 1.0F) { ++ this.i = 1.0F; ++ } ++ ++ } ++ } ++ ++ @Override ++ public void load(IBlockData iblockdata, NBTTagCompound nbttagcompound) { ++ super.load(iblockdata, nbttagcompound); ++ this.a = GameProfileSerializer.c(nbttagcompound.getCompound("blockState")); ++ this.b = EnumDirection.fromType1(nbttagcompound.getInt("facing")); ++ this.i = nbttagcompound.getFloat("progress"); ++ this.j = this.i; ++ this.c = nbttagcompound.getBoolean("extending"); ++ this.g = nbttagcompound.getBoolean("source"); ++ } ++ ++ @Override ++ public NBTTagCompound save(NBTTagCompound nbttagcompound) { ++ super.save(nbttagcompound); ++ nbttagcompound.set("blockState", GameProfileSerializer.a(this.a)); ++ nbttagcompound.setInt("facing", this.b.c()); ++ nbttagcompound.setFloat("progress", this.j); ++ nbttagcompound.setBoolean("extending", this.c); ++ nbttagcompound.setBoolean("source", this.g); ++ return nbttagcompound; ++ } ++ ++ public VoxelShape a(IBlockAccess iblockaccess, BlockPosition blockposition) { ++ VoxelShape voxelshape; ++ ++ if (!this.c && this.g) { ++ voxelshape = ((IBlockData) this.a.set(BlockPiston.EXTENDED, true)).getCollisionShape(iblockaccess, blockposition); ++ } else { ++ voxelshape = VoxelShapes.a(); ++ } ++ ++ EnumDirection enumdirection = (EnumDirection) TileEntityPiston.h.get(); ++ ++ if ((double) this.i < 1.0D && enumdirection == this.j()) { ++ return voxelshape; ++ } else { ++ IBlockData iblockdata; ++ ++ if (this.h()) { ++ iblockdata = (IBlockData) ((IBlockData) Blocks.PISTON_HEAD.getBlockData().set(BlockPistonExtension.FACING, this.b)).set(BlockPistonExtension.SHORT, this.c != 1.0F - this.i < 0.25F); ++ } else { ++ iblockdata = this.a; ++ } ++ ++ float f = this.e(this.i); ++ double d0 = (double) ((float) this.b.getAdjacentX() * f); ++ double d1 = (double) ((float) this.b.getAdjacentY() * f); ++ double d2 = (double) ((float) this.b.getAdjacentZ() * f); ++ ++ return VoxelShapes.a(voxelshape, iblockdata.getCollisionShape(iblockaccess, blockposition).a(d0, d1, d2)); ++ } ++ } ++ ++ public long m() { ++ return this.k; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/state/IBlockData.java b/src/main/java/net/minecraft/world/level/block/state/IBlockData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9e1ebfd7befe9e2fc3396b4dcd5e8fc7c23ee305 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/state/IBlockData.java +@@ -0,0 +1,22 @@ ++package net.minecraft.world.level.block.state; ++ ++import com.google.common.collect.ImmutableMap; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.MapCodec; ++import net.minecraft.core.IRegistry; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.state.properties.IBlockState; ++ ++public class IBlockData extends BlockBase.BlockData { ++ ++ public static final Codec b = a((Codec) IRegistry.BLOCK, Block::getBlockData).stable(); ++ ++ public IBlockData(Block block, ImmutableMap, Comparable> immutablemap, MapCodec mapcodec) { ++ super(block, immutablemap, mapcodec); ++ } ++ ++ @Override ++ protected IBlockData p() { ++ return this; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/state/IBlockDataHolder.java b/src/main/java/net/minecraft/world/level/block/state/IBlockDataHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..074bd5f060c6bb80568b72d23ce84c27ba774578 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/state/IBlockDataHolder.java +@@ -0,0 +1,170 @@ ++package net.minecraft.world.level.block.state; ++ ++import com.google.common.collect.ArrayTable; ++import com.google.common.collect.HashBasedTable; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Maps; ++import com.google.common.collect.Table; ++import com.google.common.collect.UnmodifiableIterator; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.MapCodec; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Map.Entry; ++import java.util.Optional; ++import java.util.function.Function; ++import java.util.stream.Collectors; ++import javax.annotation.Nullable; ++import net.minecraft.world.level.block.state.properties.IBlockState; ++ ++public abstract class IBlockDataHolder { ++ ++ public static final Function, Comparable>, String> STATE_TO_VALUE = new Function, Comparable>, String>() { ++ public String apply(@Nullable Entry, Comparable> entry) { ++ if (entry == null) { ++ return ""; ++ } else { ++ IBlockState iblockstate = (IBlockState) entry.getKey(); ++ ++ return iblockstate.getName() + "=" + this.a(iblockstate, (Comparable) entry.getValue()); ++ } ++ } ++ ++ private > String a(IBlockState iblockstate, Comparable comparable) { ++ return iblockstate.a(comparable); ++ } ++ }; ++ protected final O c; ++ private final ImmutableMap, Comparable> b; ++ private Table, Comparable, S> e; ++ protected final MapCodec d; ++ ++ protected IBlockDataHolder(O o0, ImmutableMap, Comparable> immutablemap, MapCodec mapcodec) { ++ this.c = o0; ++ this.b = immutablemap; ++ this.d = mapcodec; ++ } ++ ++ public > S a(IBlockState iblockstate) { ++ return this.set(iblockstate, (Comparable) a(iblockstate.getValues(), (Object) this.get(iblockstate))); ++ } ++ ++ protected static T a(Collection collection, T t0) { ++ Iterator iterator = collection.iterator(); ++ ++ do { ++ if (!iterator.hasNext()) { ++ return iterator.next(); ++ } ++ } while (!iterator.next().equals(t0)); ++ ++ if (iterator.hasNext()) { ++ return iterator.next(); ++ } else { ++ return collection.iterator().next(); ++ } ++ } ++ ++ public String toString() { ++ StringBuilder stringbuilder = new StringBuilder(); ++ ++ stringbuilder.append(this.c); ++ if (!this.getStateMap().isEmpty()) { ++ stringbuilder.append('['); ++ stringbuilder.append((String) this.getStateMap().entrySet().stream().map(IBlockDataHolder.STATE_TO_VALUE).collect(Collectors.joining(","))); ++ stringbuilder.append(']'); ++ } ++ ++ return stringbuilder.toString(); ++ } ++ ++ public Collection> r() { ++ return Collections.unmodifiableCollection(this.b.keySet()); ++ } ++ ++ public > boolean b(IBlockState iblockstate) { ++ return this.b.containsKey(iblockstate); ++ } ++ ++ public > T get(IBlockState iblockstate) { ++ Comparable comparable = (Comparable) this.b.get(iblockstate); ++ ++ if (comparable == null) { ++ throw new IllegalArgumentException("Cannot get property " + iblockstate + " as it does not exist in " + this.c); ++ } else { ++ return (Comparable) iblockstate.getType().cast(comparable); ++ } ++ } ++ ++ public > Optional d(IBlockState iblockstate) { ++ Comparable comparable = (Comparable) this.b.get(iblockstate); ++ ++ return comparable == null ? Optional.empty() : Optional.of(iblockstate.getType().cast(comparable)); ++ } ++ ++ public , V extends T> S set(IBlockState iblockstate, V v0) { ++ Comparable comparable = (Comparable) this.b.get(iblockstate); ++ ++ if (comparable == null) { ++ throw new IllegalArgumentException("Cannot set property " + iblockstate + " as it does not exist in " + this.c); ++ } else if (comparable == v0) { ++ return this; ++ } else { ++ S s0 = this.e.get(iblockstate, v0); ++ ++ if (s0 == null) { ++ throw new IllegalArgumentException("Cannot set property " + iblockstate + " to " + v0 + " on " + this.c + ", it is not an allowed value"); ++ } else { ++ return s0; ++ } ++ } ++ } ++ ++ public void a(Map, Comparable>, S> map) { ++ if (this.e != null) { ++ throw new IllegalStateException(); ++ } else { ++ Table, Comparable, S> table = HashBasedTable.create(); ++ UnmodifiableIterator unmodifiableiterator = this.b.entrySet().iterator(); ++ ++ while (unmodifiableiterator.hasNext()) { ++ Entry, Comparable> entry = (Entry) unmodifiableiterator.next(); ++ IBlockState iblockstate = (IBlockState) entry.getKey(); ++ Iterator iterator = iblockstate.getValues().iterator(); ++ ++ while (iterator.hasNext()) { ++ Comparable comparable = (Comparable) iterator.next(); ++ ++ if (comparable != entry.getValue()) { ++ table.put(iblockstate, comparable, map.get(this.b(iblockstate, comparable))); ++ } ++ } ++ } ++ ++ this.e = (Table) (table.isEmpty() ? table : ArrayTable.create(table)); ++ } ++ } ++ ++ private Map, Comparable> b(IBlockState iblockstate, Comparable comparable) { ++ Map, Comparable> map = Maps.newHashMap(this.b); ++ ++ map.put(iblockstate, comparable); ++ return map; ++ } ++ ++ public ImmutableMap, Comparable> getStateMap() { ++ return this.b; ++ } ++ ++ protected static > Codec a(Codec codec, Function function) { ++ return codec.dispatch("Name", (iblockdataholder) -> { ++ return iblockdataholder.c; ++ }, (object) -> { ++ S s0 = (IBlockDataHolder) function.apply(object); ++ ++ return s0.getStateMap().isEmpty() ? Codec.unit(s0) : s0.d.fieldOf("Properties").codec(); ++ }); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateBoolean.java b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateBoolean.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0701c1a178852345b6bf01bce8b1d0559c535d45 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateBoolean.java +@@ -0,0 +1,50 @@ ++package net.minecraft.world.level.block.state.properties; ++ ++import com.google.common.collect.ImmutableSet; ++import java.util.Collection; ++import java.util.Optional; ++ ++public class BlockStateBoolean extends IBlockState { ++ ++ private final ImmutableSet a = ImmutableSet.of(true, false); ++ ++ protected BlockStateBoolean(String s) { ++ super(s, Boolean.class); ++ } ++ ++ @Override ++ public Collection getValues() { ++ return this.a; ++ } ++ ++ public static BlockStateBoolean of(String s) { ++ return new BlockStateBoolean(s); ++ } ++ ++ @Override ++ public Optional b(String s) { ++ return !"true".equals(s) && !"false".equals(s) ? Optional.empty() : Optional.of(Boolean.valueOf(s)); ++ } ++ ++ public String a(Boolean obool) { ++ return obool.toString(); ++ } ++ ++ @Override ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } else if (object instanceof BlockStateBoolean && super.equals(object)) { ++ BlockStateBoolean blockstateboolean = (BlockStateBoolean) object; ++ ++ return this.a.equals(blockstateboolean.a); ++ } else { ++ return false; ++ } ++ } ++ ++ @Override ++ public int b() { ++ return 31 * super.b() + this.a.hashCode(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateEnum.java b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateEnum.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a6aaf0efed5a9c5e458ca04a80a7a5e71a31d886 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateEnum.java +@@ -0,0 +1,90 @@ ++package net.minecraft.world.level.block.state.properties; ++ ++import com.google.common.base.Predicates; ++import com.google.common.collect.ImmutableSet; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import java.util.Arrays; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Optional; ++import java.util.function.Predicate; ++import java.util.stream.Collectors; ++import net.minecraft.util.INamable; ++ ++public class BlockStateEnum & INamable> extends IBlockState { ++ ++ private final ImmutableSet a; ++ private final Map b = Maps.newHashMap(); ++ ++ protected BlockStateEnum(String s, Class oclass, Collection collection) { ++ super(s, oclass); ++ this.a = ImmutableSet.copyOf(collection); ++ Iterator iterator = collection.iterator(); ++ ++ while (iterator.hasNext()) { ++ T t0 = (Enum) iterator.next(); ++ String s1 = ((INamable) t0).getName(); ++ ++ if (this.b.containsKey(s1)) { ++ throw new IllegalArgumentException("Multiple values have the same name '" + s1 + "'"); ++ } ++ ++ this.b.put(s1, t0); ++ } ++ ++ } ++ ++ @Override ++ public Collection getValues() { ++ return this.a; ++ } ++ ++ @Override ++ public Optional b(String s) { ++ return Optional.ofNullable(this.b.get(s)); ++ } ++ ++ public String a(T t0) { ++ return ((INamable) t0).getName(); ++ } ++ ++ @Override ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } else if (object instanceof BlockStateEnum && super.equals(object)) { ++ BlockStateEnum blockstateenum = (BlockStateEnum) object; ++ ++ return this.a.equals(blockstateenum.a) && this.b.equals(blockstateenum.b); ++ } else { ++ return false; ++ } ++ } ++ ++ @Override ++ public int b() { ++ int i = super.b(); ++ ++ i = 31 * i + this.a.hashCode(); ++ i = 31 * i + this.b.hashCode(); ++ return i; ++ } ++ ++ public static & INamable> BlockStateEnum of(String s, Class oclass) { ++ return a(s, oclass, (Predicate) Predicates.alwaysTrue()); ++ } ++ ++ public static & INamable> BlockStateEnum a(String s, Class oclass, Predicate predicate) { ++ return a(s, oclass, (Collection) Arrays.stream(oclass.getEnumConstants()).filter(predicate).collect(Collectors.toList())); ++ } ++ ++ public static & INamable> BlockStateEnum of(String s, Class oclass, T... at) { ++ return a(s, oclass, (Collection) Lists.newArrayList(at)); ++ } ++ ++ public static & INamable> BlockStateEnum a(String s, Class oclass, Collection collection) { ++ return new BlockStateEnum<>(s, oclass, collection); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IBlockState.java b/src/main/java/net/minecraft/world/level/block/state/properties/IBlockState.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3e6ba74027685c6190426c825736e84cda87ca63 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/IBlockState.java +@@ -0,0 +1,133 @@ ++package net.minecraft.world.level.block.state.properties; ++ ++import com.google.common.base.MoreObjects; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.DataResult; ++import java.util.Collection; ++import java.util.Optional; ++import java.util.stream.Stream; ++import net.minecraft.world.level.block.state.IBlockDataHolder; ++ ++public abstract class IBlockState> { ++ ++ private final Class a; ++ private final String b; ++ private Integer c; ++ private final Codec d; ++ private final Codec> e; ++ ++ protected IBlockState(String s, Class oclass) { ++ this.d = Codec.STRING.comapFlatMap((s1) -> { ++ return (DataResult) this.b(s1).map(DataResult::success).orElseGet(() -> { ++ return DataResult.error("Unable to read property: " + this + " with value: " + s1); ++ }); ++ }, this::a); ++ this.e = this.d.xmap(this::b, IBlockState.a::b); ++ this.a = oclass; ++ this.b = s; ++ } ++ ++ public IBlockState.a b(T t0) { ++ return new IBlockState.a<>(this, t0); ++ } ++ ++ public IBlockState.a a(IBlockDataHolder iblockdataholder) { ++ return new IBlockState.a<>(this, iblockdataholder.get(this)); ++ } ++ ++ public Stream> c() { ++ return this.getValues().stream().map(this::b); ++ } ++ ++ public Codec> e() { ++ return this.e; ++ } ++ ++ public String getName() { ++ return this.b; ++ } ++ ++ public Class getType() { ++ return this.a; ++ } ++ ++ public abstract Collection getValues(); ++ ++ public abstract String a(T t0); ++ ++ public abstract Optional b(String s); ++ ++ public String toString() { ++ return MoreObjects.toStringHelper(this).add("name", this.b).add("clazz", this.a).add("values", this.getValues()).toString(); ++ } ++ ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } else if (!(object instanceof IBlockState)) { ++ return false; ++ } else { ++ IBlockState iblockstate = (IBlockState) object; ++ ++ return this.a.equals(iblockstate.a) && this.b.equals(iblockstate.b); ++ } ++ } ++ ++ public final int hashCode() { ++ if (this.c == null) { ++ this.c = this.b(); ++ } ++ ++ return this.c; ++ } ++ ++ public int b() { ++ return 31 * this.a.hashCode() + this.b.hashCode(); ++ } ++ ++ public static final class a> { ++ ++ private final IBlockState a; ++ private final T b; ++ ++ private a(IBlockState iblockstate, T t0) { ++ if (!iblockstate.getValues().contains(t0)) { ++ throw new IllegalArgumentException("Value " + t0 + " does not belong to property " + iblockstate); ++ } else { ++ this.a = iblockstate; ++ this.b = t0; ++ } ++ } ++ ++ public IBlockState a() { ++ return this.a; ++ } ++ ++ public T b() { ++ return this.b; ++ } ++ ++ public String toString() { ++ return this.a.getName() + "=" + this.a.a(this.b); ++ } ++ ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } else if (!(object instanceof IBlockState.a)) { ++ return false; ++ } else { ++ IBlockState.a iblockstate_a = (IBlockState.a) object; ++ ++ return this.a == iblockstate_a.a && this.b.equals(iblockstate_a.b); ++ } ++ } ++ ++ public int hashCode() { ++ int i = this.a.hashCode(); ++ ++ i = 31 * i + this.b.hashCode(); ++ return i; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkConverter.java b/src/main/java/net/minecraft/world/level/chunk/ChunkConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..60ecd3a92af0f1968b10bb8babfb43147ef568d3 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkConverter.java +@@ -0,0 +1,390 @@ ++package net.minecraft.world.level.chunk; ++ ++import com.google.common.collect.Lists; ++import com.google.common.collect.Sets; ++import it.unimi.dsi.fastutil.objects.ObjectIterator; ++import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; ++import it.unimi.dsi.fastutil.objects.ObjectSet; ++import java.util.EnumSet; ++import java.util.IdentityHashMap; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import net.minecraft.core.BaseBlockPosition; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.core.EnumDirection8; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.GeneratorAccess; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.BlockChest; ++import net.minecraft.world.level.block.BlockFacingHorizontal; ++import net.minecraft.world.level.block.BlockStem; ++import net.minecraft.world.level.block.BlockStemmed; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.entity.TileEntityChest; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.block.state.properties.BlockProperties; ++import net.minecraft.world.level.block.state.properties.BlockPropertyChestType; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class ChunkConverter { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ public static final ChunkConverter a = new ChunkConverter(); ++ private static final EnumDirection8[] c = EnumDirection8.values(); ++ private final EnumSet d; ++ private final int[][] e; ++ private static final Map f = new IdentityHashMap(); ++ private static final Set g = Sets.newHashSet(); ++ ++ private ChunkConverter() { ++ this.d = EnumSet.noneOf(EnumDirection8.class); ++ this.e = new int[16][]; ++ } ++ ++ public ChunkConverter(NBTTagCompound nbttagcompound) { ++ this(); ++ if (nbttagcompound.hasKeyOfType("Indices", 10)) { ++ NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Indices"); ++ ++ for (int i = 0; i < this.e.length; ++i) { ++ String s = String.valueOf(i); ++ ++ if (nbttagcompound1.hasKeyOfType(s, 11)) { ++ this.e[i] = nbttagcompound1.getIntArray(s); ++ } ++ } ++ } ++ ++ int j = nbttagcompound.getInt("Sides"); ++ EnumDirection8[] aenumdirection8 = EnumDirection8.values(); ++ int k = aenumdirection8.length; ++ ++ for (int l = 0; l < k; ++l) { ++ EnumDirection8 enumdirection8 = aenumdirection8[l]; ++ ++ if ((j & 1 << enumdirection8.ordinal()) != 0) { ++ this.d.add(enumdirection8); ++ } ++ } ++ ++ } ++ ++ public void a(Chunk chunk) { ++ this.b(chunk); ++ EnumDirection8[] aenumdirection8 = ChunkConverter.c; ++ int i = aenumdirection8.length; ++ ++ for (int j = 0; j < i; ++j) { ++ EnumDirection8 enumdirection8 = aenumdirection8[j]; ++ ++ a(chunk, enumdirection8); ++ } ++ ++ World world = chunk.getWorld(); ++ ++ ChunkConverter.g.forEach((chunkconverter_a) -> { ++ chunkconverter_a.a(world); ++ }); ++ } ++ ++ private static void a(Chunk chunk, EnumDirection8 enumdirection8) { ++ World world = chunk.getWorld(); ++ ++ if (chunk.p().d.remove(enumdirection8)) { ++ Set set = enumdirection8.a(); ++ boolean flag = false; ++ boolean flag1 = true; ++ boolean flag2 = set.contains(EnumDirection.EAST); ++ boolean flag3 = set.contains(EnumDirection.WEST); ++ boolean flag4 = set.contains(EnumDirection.SOUTH); ++ boolean flag5 = set.contains(EnumDirection.NORTH); ++ boolean flag6 = set.size() == 1; ++ ChunkCoordIntPair chunkcoordintpair = chunk.getPos(); ++ int i = chunkcoordintpair.d() + (flag6 && (flag5 || flag4) ? 1 : (flag3 ? 0 : 15)); ++ int j = chunkcoordintpair.d() + (flag6 && (flag5 || flag4) ? 14 : (flag3 ? 0 : 15)); ++ int k = chunkcoordintpair.e() + (flag6 && (flag2 || flag3) ? 1 : (flag5 ? 0 : 15)); ++ int l = chunkcoordintpair.e() + (flag6 && (flag2 || flag3) ? 14 : (flag5 ? 0 : 15)); ++ EnumDirection[] aenumdirection = EnumDirection.values(); ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ Iterator iterator = BlockPosition.b(i, 0, k, j, world.getBuildHeight() - 1, l).iterator(); ++ ++ while (iterator.hasNext()) { ++ BlockPosition blockposition = (BlockPosition) iterator.next(); ++ IBlockData iblockdata = world.getType(blockposition); ++ IBlockData iblockdata1 = iblockdata; ++ EnumDirection[] aenumdirection1 = aenumdirection; ++ int i1 = aenumdirection.length; ++ ++ for (int j1 = 0; j1 < i1; ++j1) { ++ EnumDirection enumdirection = aenumdirection1[j1]; ++ ++ blockposition_mutableblockposition.a((BaseBlockPosition) blockposition, enumdirection); ++ iblockdata1 = a(iblockdata1, enumdirection, world, blockposition, blockposition_mutableblockposition); ++ } ++ ++ Block.a(iblockdata, iblockdata1, world, blockposition, 18); ++ } ++ ++ } ++ } ++ ++ private static IBlockData a(IBlockData iblockdata, EnumDirection enumdirection, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { ++ return ((ChunkConverter.a) ChunkConverter.f.getOrDefault(iblockdata.getBlock(), ChunkConverter.Type.DEFAULT)).a(iblockdata, enumdirection, generatoraccess.getType(blockposition1), generatoraccess, blockposition, blockposition1); ++ } ++ ++ private void b(Chunk chunk) { ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition1 = new BlockPosition.MutableBlockPosition(); ++ ChunkCoordIntPair chunkcoordintpair = chunk.getPos(); ++ World world = chunk.getWorld(); ++ ++ int i; ++ ++ for (i = 0; i < 16; ++i) { ++ ChunkSection chunksection = chunk.getSections()[i]; ++ int[] aint = this.e[i]; ++ ++ this.e[i] = null; ++ if (chunksection != null && aint != null && aint.length > 0) { ++ EnumDirection[] aenumdirection = EnumDirection.values(); ++ DataPaletteBlock datapaletteblock = chunksection.getBlocks(); ++ int[] aint1 = aint; ++ int j = aint.length; ++ ++ for (int k = 0; k < j; ++k) { ++ int l = aint1[k]; ++ int i1 = l & 15; ++ int j1 = l >> 8 & 15; ++ int k1 = l >> 4 & 15; ++ ++ blockposition_mutableblockposition.d(chunkcoordintpair.d() + i1, (i << 4) + j1, chunkcoordintpair.e() + k1); ++ IBlockData iblockdata = (IBlockData) datapaletteblock.a(l); ++ IBlockData iblockdata1 = iblockdata; ++ EnumDirection[] aenumdirection1 = aenumdirection; ++ int l1 = aenumdirection.length; ++ ++ for (int i2 = 0; i2 < l1; ++i2) { ++ EnumDirection enumdirection = aenumdirection1[i2]; ++ ++ blockposition_mutableblockposition1.a((BaseBlockPosition) blockposition_mutableblockposition, enumdirection); ++ if (blockposition_mutableblockposition.getX() >> 4 == chunkcoordintpair.x && blockposition_mutableblockposition.getZ() >> 4 == chunkcoordintpair.z) { ++ iblockdata1 = a(iblockdata1, enumdirection, world, blockposition_mutableblockposition, blockposition_mutableblockposition1); ++ } ++ } ++ ++ Block.a(iblockdata, iblockdata1, world, blockposition_mutableblockposition, 18); ++ } ++ } ++ } ++ ++ for (i = 0; i < this.e.length; ++i) { ++ if (this.e[i] != null) { ++ ChunkConverter.LOGGER.warn("Discarding update data for section {} for chunk ({} {})", i, chunkcoordintpair.x, chunkcoordintpair.z); ++ } ++ ++ this.e[i] = null; ++ } ++ ++ } ++ ++ public boolean a() { ++ int[][] aint = this.e; ++ int i = aint.length; ++ ++ for (int j = 0; j < i; ++j) { ++ int[] aint1 = aint[j]; ++ ++ if (aint1 != null) { ++ return false; ++ } ++ } ++ ++ return this.d.isEmpty(); ++ } ++ ++ public NBTTagCompound b() { ++ NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ NBTTagCompound nbttagcompound1 = new NBTTagCompound(); ++ ++ int i; ++ ++ for (i = 0; i < this.e.length; ++i) { ++ String s = String.valueOf(i); ++ ++ if (this.e[i] != null && this.e[i].length != 0) { ++ nbttagcompound1.setIntArray(s, this.e[i]); ++ } ++ } ++ ++ if (!nbttagcompound1.isEmpty()) { ++ nbttagcompound.set("Indices", nbttagcompound1); ++ } ++ ++ i = 0; ++ ++ EnumDirection8 enumdirection8; ++ ++ for (Iterator iterator = this.d.iterator(); iterator.hasNext(); i |= 1 << enumdirection8.ordinal()) { ++ enumdirection8 = (EnumDirection8) iterator.next(); ++ } ++ ++ nbttagcompound.setByte("Sides", (byte) i); ++ return nbttagcompound; ++ } ++ ++ static enum Type implements ChunkConverter.a { ++ ++ BLACKLIST(new Block[]{Blocks.OBSERVER, Blocks.NETHER_PORTAL, Blocks.WHITE_CONCRETE_POWDER, Blocks.ORANGE_CONCRETE_POWDER, Blocks.MAGENTA_CONCRETE_POWDER, Blocks.LIGHT_BLUE_CONCRETE_POWDER, Blocks.YELLOW_CONCRETE_POWDER, Blocks.LIME_CONCRETE_POWDER, Blocks.PINK_CONCRETE_POWDER, Blocks.GRAY_CONCRETE_POWDER, Blocks.LIGHT_GRAY_CONCRETE_POWDER, Blocks.CYAN_CONCRETE_POWDER, Blocks.PURPLE_CONCRETE_POWDER, Blocks.BLUE_CONCRETE_POWDER, Blocks.BROWN_CONCRETE_POWDER, Blocks.GREEN_CONCRETE_POWDER, Blocks.RED_CONCRETE_POWDER, Blocks.BLACK_CONCRETE_POWDER, Blocks.ANVIL, Blocks.CHIPPED_ANVIL, Blocks.DAMAGED_ANVIL, Blocks.DRAGON_EGG, Blocks.GRAVEL, Blocks.SAND, Blocks.RED_SAND, Blocks.OAK_SIGN, Blocks.SPRUCE_SIGN, Blocks.BIRCH_SIGN, Blocks.ACACIA_SIGN, Blocks.JUNGLE_SIGN, Blocks.DARK_OAK_SIGN, Blocks.OAK_WALL_SIGN, Blocks.SPRUCE_WALL_SIGN, Blocks.BIRCH_WALL_SIGN, Blocks.ACACIA_WALL_SIGN, Blocks.JUNGLE_WALL_SIGN, Blocks.DARK_OAK_WALL_SIGN}) { ++ @Override ++ public IBlockData a(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { ++ return iblockdata; ++ } ++ }, ++ DEFAULT(new Block[0]) { ++ @Override ++ public IBlockData a(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { ++ return iblockdata.updateState(enumdirection, generatoraccess.getType(blockposition1), generatoraccess, blockposition, blockposition1); ++ } ++ }, ++ CHEST(new Block[]{Blocks.CHEST, Blocks.TRAPPED_CHEST}) { ++ @Override ++ public IBlockData a(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { ++ if (iblockdata1.a(iblockdata.getBlock()) && enumdirection.n().d() && iblockdata.get(BlockChest.c) == BlockPropertyChestType.SINGLE && iblockdata1.get(BlockChest.c) == BlockPropertyChestType.SINGLE) { ++ EnumDirection enumdirection1 = (EnumDirection) iblockdata.get(BlockChest.FACING); ++ ++ if (enumdirection.n() != enumdirection1.n() && enumdirection1 == iblockdata1.get(BlockChest.FACING)) { ++ BlockPropertyChestType blockpropertychesttype = enumdirection == enumdirection1.g() ? BlockPropertyChestType.LEFT : BlockPropertyChestType.RIGHT; ++ ++ generatoraccess.setTypeAndData(blockposition1, (IBlockData) iblockdata1.set(BlockChest.c, blockpropertychesttype.b()), 18); ++ if (enumdirection1 == EnumDirection.NORTH || enumdirection1 == EnumDirection.EAST) { ++ TileEntity tileentity = generatoraccess.getTileEntity(blockposition); ++ TileEntity tileentity1 = generatoraccess.getTileEntity(blockposition1); ++ ++ if (tileentity instanceof TileEntityChest && tileentity1 instanceof TileEntityChest) { ++ TileEntityChest.a((TileEntityChest) tileentity, (TileEntityChest) tileentity1); ++ } ++ } ++ ++ return (IBlockData) iblockdata.set(BlockChest.c, blockpropertychesttype); ++ } ++ } ++ ++ return iblockdata; ++ } ++ }, ++ LEAVES(true, new Block[]{Blocks.ACACIA_LEAVES, Blocks.BIRCH_LEAVES, Blocks.DARK_OAK_LEAVES, Blocks.JUNGLE_LEAVES, Blocks.OAK_LEAVES, Blocks.SPRUCE_LEAVES}) { ++ private final ThreadLocal>> g = ThreadLocal.withInitial(() -> { ++ return Lists.newArrayListWithCapacity(7); ++ }); ++ ++ @Override ++ public IBlockData a(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { ++ IBlockData iblockdata2 = iblockdata.updateState(enumdirection, generatoraccess.getType(blockposition1), generatoraccess, blockposition, blockposition1); ++ ++ if (iblockdata != iblockdata2) { ++ int i = (Integer) iblockdata2.get(BlockProperties.an); ++ List> list = (List) this.g.get(); ++ ++ if (list.isEmpty()) { ++ for (int j = 0; j < 7; ++j) { ++ list.add(new ObjectOpenHashSet()); ++ } ++ } ++ ++ ((ObjectSet) list.get(i)).add(blockposition.immutableCopy()); ++ } ++ ++ return iblockdata; ++ } ++ ++ @Override ++ public void a(GeneratorAccess generatoraccess) { ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ List> list = (List) this.g.get(); ++ ++ for (int i = 2; i < list.size(); ++i) { ++ int j = i - 1; ++ ObjectSet objectset = (ObjectSet) list.get(j); ++ ObjectSet objectset1 = (ObjectSet) list.get(i); ++ ObjectIterator objectiterator = objectset.iterator(); ++ ++ while (objectiterator.hasNext()) { ++ BlockPosition blockposition = (BlockPosition) objectiterator.next(); ++ IBlockData iblockdata = generatoraccess.getType(blockposition); ++ ++ if ((Integer) iblockdata.get(BlockProperties.an) >= j) { ++ generatoraccess.setTypeAndData(blockposition, (IBlockData) iblockdata.set(BlockProperties.an, j), 18); ++ if (i != 7) { ++ EnumDirection[] aenumdirection = null.f; ++ int k = aenumdirection.length; ++ ++ for (int l = 0; l < k; ++l) { ++ EnumDirection enumdirection = aenumdirection[l]; ++ ++ blockposition_mutableblockposition.a((BaseBlockPosition) blockposition, enumdirection); ++ IBlockData iblockdata1 = generatoraccess.getType(blockposition_mutableblockposition); ++ ++ if (iblockdata1.b(BlockProperties.an) && (Integer) iblockdata.get(BlockProperties.an) > i) { ++ objectset1.add(blockposition_mutableblockposition.immutableCopy()); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ list.clear(); ++ } ++ }, ++ STEM_BLOCK(new Block[]{Blocks.MELON_STEM, Blocks.PUMPKIN_STEM}) { ++ @Override ++ public IBlockData a(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { ++ if ((Integer) iblockdata.get(BlockStem.AGE) == 7) { ++ BlockStemmed blockstemmed = ((BlockStem) iblockdata.getBlock()).d(); ++ ++ if (iblockdata1.a((Block) blockstemmed)) { ++ return (IBlockData) blockstemmed.d().getBlockData().set(BlockFacingHorizontal.FACING, enumdirection); ++ } ++ } ++ ++ return iblockdata; ++ } ++ }; ++ ++ public static final EnumDirection[] f = EnumDirection.values(); ++ ++ private Type(Block... ablock) { ++ this(false, ablock); ++ } ++ ++ private Type(boolean flag, Block... ablock) { ++ Block[] ablock1 = ablock; ++ int i = ablock.length; ++ ++ for (int j = 0; j < i; ++j) { ++ Block block = ablock1[j]; ++ ++ ChunkConverter.f.put(block, this); ++ } ++ ++ if (flag) { ++ ChunkConverter.g.add(this); ++ } ++ ++ } ++ } ++ ++ public interface a { ++ ++ IBlockData a(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1); ++ ++ default void a(GeneratorAccess generatoraccess) {} ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkEmpty.java b/src/main/java/net/minecraft/world/level/chunk/ChunkEmpty.java +new file mode 100644 +index 0000000000000000000000000000000000000000..395d21afaabcbd99f9ce0551d647f5db9507a518 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkEmpty.java +@@ -0,0 +1,108 @@ ++package net.minecraft.world.level.chunk; ++ ++import java.util.Arrays; ++import java.util.List; ++import java.util.function.Predicate; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.IRegistry; ++import net.minecraft.data.worldgen.biome.BiomeRegistry; ++import net.minecraft.server.level.PlayerChunk; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.biome.BiomeBase; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.lighting.LightEngine; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.level.material.FluidTypes; ++import net.minecraft.world.phys.AxisAlignedBB; ++ ++public class ChunkEmpty extends Chunk { ++ ++ private static final BiomeBase[] b = (BiomeBase[]) SystemUtils.a((Object) (new BiomeBase[BiomeStorage.a]), (abiomebase) -> { ++ Arrays.fill(abiomebase, BiomeRegistry.a); ++ }); ++ ++ public ChunkEmpty(World world, ChunkCoordIntPair chunkcoordintpair) { ++ super(world, chunkcoordintpair, new BiomeStorage(world.r().b(IRegistry.ay), ChunkEmpty.b)); ++ } ++ ++ @Override ++ public IBlockData getType(BlockPosition blockposition) { ++ return Blocks.VOID_AIR.getBlockData(); ++ } ++ ++ @Nullable ++ @Override ++ public IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag) { ++ return null; ++ } ++ ++ @Override ++ public Fluid getFluid(BlockPosition blockposition) { ++ return FluidTypes.EMPTY.h(); ++ } ++ ++ @Nullable ++ @Override ++ public LightEngine e() { ++ return null; ++ } ++ ++ @Override ++ public int g(BlockPosition blockposition) { ++ return 0; ++ } ++ ++ @Override ++ public void a(Entity entity) {} ++ ++ @Override ++ public void b(Entity entity) {} ++ ++ @Override ++ public void a(Entity entity, int i) {} ++ ++ @Nullable ++ @Override ++ public TileEntity a(BlockPosition blockposition, Chunk.EnumTileEntityState chunk_enumtileentitystate) { ++ return null; ++ } ++ ++ @Override ++ public void a(TileEntity tileentity) {} ++ ++ @Override ++ public void setTileEntity(BlockPosition blockposition, TileEntity tileentity) {} ++ ++ @Override ++ public void removeTileEntity(BlockPosition blockposition) {} ++ ++ @Override ++ public void markDirty() {} ++ ++ @Override ++ public void a(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, Predicate predicate) {} ++ ++ @Override ++ public void a(Class oclass, AxisAlignedBB axisalignedbb, List list, Predicate predicate) {} ++ ++ @Override ++ public boolean isEmpty() { ++ return true; ++ } ++ ++ @Override ++ public boolean a(int i, int j) { ++ return true; ++ } ++ ++ @Override ++ public PlayerChunk.State getState() { ++ return PlayerChunk.State.BORDER; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cf2dd6da5ce88aafdcc4db63af18eda9396a066a +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +@@ -0,0 +1,154 @@ ++package net.minecraft.world.level.chunk; ++ ++import java.util.function.Predicate; ++import javax.annotation.Nullable; ++import net.minecraft.nbt.GameProfileSerializer; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.material.Fluid; ++ ++public class ChunkSection { ++ ++ public static final DataPalette GLOBAL_PALETTE = new DataPaletteGlobal<>(Block.REGISTRY_ID, Blocks.AIR.getBlockData()); ++ private final int yPos; ++ private short nonEmptyBlockCount; ++ private short tickingBlockCount; ++ private short e; ++ private final DataPaletteBlock blockIds; ++ ++ public ChunkSection(int i) { ++ this(i, (short) 0, (short) 0, (short) 0); ++ } ++ ++ public ChunkSection(int i, short short0, short short1, short short2) { ++ this.yPos = i; ++ this.nonEmptyBlockCount = short0; ++ this.tickingBlockCount = short1; ++ this.e = short2; ++ this.blockIds = new DataPaletteBlock<>(ChunkSection.GLOBAL_PALETTE, Block.REGISTRY_ID, GameProfileSerializer::c, GameProfileSerializer::a, Blocks.AIR.getBlockData()); ++ } ++ ++ public IBlockData getType(int i, int j, int k) { ++ return (IBlockData) this.blockIds.a(i, j, k); ++ } ++ ++ public Fluid b(int i, int j, int k) { ++ return ((IBlockData) this.blockIds.a(i, j, k)).getFluid(); ++ } ++ ++ public void a() { ++ this.blockIds.a(); ++ } ++ ++ public void b() { ++ this.blockIds.b(); ++ } ++ ++ public IBlockData setType(int i, int j, int k, IBlockData iblockdata) { ++ return this.setType(i, j, k, iblockdata, true); ++ } ++ ++ public IBlockData setType(int i, int j, int k, IBlockData iblockdata, boolean flag) { ++ IBlockData iblockdata1; ++ ++ if (flag) { ++ iblockdata1 = (IBlockData) this.blockIds.setBlock(i, j, k, iblockdata); ++ } else { ++ iblockdata1 = (IBlockData) this.blockIds.b(i, j, k, iblockdata); ++ } ++ ++ Fluid fluid = iblockdata1.getFluid(); ++ Fluid fluid1 = iblockdata.getFluid(); ++ ++ if (!iblockdata1.isAir()) { ++ --this.nonEmptyBlockCount; ++ if (iblockdata1.isTicking()) { ++ --this.tickingBlockCount; ++ } ++ } ++ ++ if (!fluid.isEmpty()) { ++ --this.e; ++ } ++ ++ if (!iblockdata.isAir()) { ++ ++this.nonEmptyBlockCount; ++ if (iblockdata.isTicking()) { ++ ++this.tickingBlockCount; ++ } ++ } ++ ++ if (!fluid1.isEmpty()) { ++ ++this.e; ++ } ++ ++ return iblockdata1; ++ } ++ ++ public boolean c() { ++ return this.nonEmptyBlockCount == 0; ++ } ++ ++ public static boolean a(@Nullable ChunkSection chunksection) { ++ return chunksection == Chunk.a || chunksection.c(); ++ } ++ ++ public boolean d() { ++ return this.shouldTick() || this.f(); ++ } ++ ++ public boolean shouldTick() { ++ return this.tickingBlockCount > 0; ++ } ++ ++ public boolean f() { ++ return this.e > 0; ++ } ++ ++ public int getYPosition() { ++ return this.yPos; ++ } ++ ++ public void recalcBlockCounts() { ++ this.nonEmptyBlockCount = 0; ++ this.tickingBlockCount = 0; ++ this.e = 0; ++ this.blockIds.a((iblockdata, i) -> { ++ Fluid fluid = iblockdata.getFluid(); ++ ++ if (!iblockdata.isAir()) { ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i); ++ if (iblockdata.isTicking()) { ++ this.tickingBlockCount = (short) (this.tickingBlockCount + i); ++ } ++ } ++ ++ if (!fluid.isEmpty()) { ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i); ++ if (fluid.f()) { ++ this.e = (short) (this.e + i); ++ } ++ } ++ ++ }); ++ } ++ ++ public DataPaletteBlock getBlocks() { ++ return this.blockIds; ++ } ++ ++ public void b(PacketDataSerializer packetdataserializer) { ++ packetdataserializer.writeShort(this.nonEmptyBlockCount); ++ this.blockIds.b(packetdataserializer); ++ } ++ ++ public int j() { ++ return 2 + this.blockIds.c(); ++ } ++ ++ public boolean a(Predicate predicate) { ++ return this.blockIds.contains(predicate); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/DataPalette.java b/src/main/java/net/minecraft/world/level/chunk/DataPalette.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f1dd62541187d007a69087f0279508b6b18d5166 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/DataPalette.java +@@ -0,0 +1,22 @@ ++package net.minecraft.world.level.chunk; ++ ++import java.util.function.Predicate; ++import javax.annotation.Nullable; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.network.PacketDataSerializer; ++ ++public interface DataPalette { ++ ++ int a(T t0); ++ ++ boolean a(Predicate predicate); ++ ++ @Nullable ++ T a(int i); ++ ++ void b(PacketDataSerializer packetdataserializer); ++ ++ int a(); ++ ++ void a(NBTTagList nbttaglist); ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fe441146757a4ac0562d5b493fb6430e33b9ee28 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +@@ -0,0 +1,242 @@ ++package net.minecraft.world.level.chunk; ++ ++import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; ++import java.util.Arrays; ++import java.util.Objects; ++import java.util.concurrent.locks.ReentrantLock; ++import java.util.function.Function; ++import java.util.function.Predicate; ++import java.util.stream.Collectors; ++import net.minecraft.CrashReport; ++import net.minecraft.CrashReportSystemDetails; ++import net.minecraft.ReportedException; ++import net.minecraft.core.RegistryBlockID; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.util.DataBits; ++import net.minecraft.util.MathHelper; ++ ++public class DataPaletteBlock implements DataPaletteExpandable { ++ ++ private final DataPalette b; ++ private final DataPaletteExpandable c = (i, object) -> { ++ return 0; ++ }; ++ private final RegistryBlockID d; ++ private final Function e; ++ private final Function f; ++ private final T g; ++ protected DataBits a; ++ private DataPalette h; ++ private int i; ++ private final ReentrantLock j = new ReentrantLock(); ++ ++ public void a() { ++ if (this.j.isLocked() && !this.j.isHeldByCurrentThread()) { ++ String s = (String) Thread.getAllStackTraces().keySet().stream().filter(Objects::nonNull).map((thread) -> { ++ return thread.getName() + ": \n\tat " + (String) Arrays.stream(thread.getStackTrace()).map(Object::toString).collect(Collectors.joining("\n\tat ")); ++ }).collect(Collectors.joining("\n")); ++ CrashReport crashreport = new CrashReport("Writing into PalettedContainer from multiple threads", new IllegalStateException()); ++ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Thread dumps"); ++ ++ crashreportsystemdetails.a("Thread dumps", (Object) s); ++ throw new ReportedException(crashreport); ++ } else { ++ this.j.lock(); ++ } ++ } ++ ++ public void b() { ++ this.j.unlock(); ++ } ++ ++ public DataPaletteBlock(DataPalette datapalette, RegistryBlockID registryblockid, Function function, Function function1, T t0) { ++ this.b = datapalette; ++ this.d = registryblockid; ++ this.e = function; ++ this.f = function1; ++ this.g = t0; ++ this.b(4); ++ } ++ ++ private static int b(int i, int j, int k) { ++ return j << 8 | k << 4 | i; ++ } ++ ++ private void b(int i) { ++ if (i != this.i) { ++ this.i = i; ++ if (this.i <= 4) { ++ this.i = 4; ++ this.h = new DataPaletteLinear<>(this.d, this.i, this, this.e); ++ } else if (this.i < 9) { ++ this.h = new DataPaletteHash<>(this.d, this.i, this, this.e, this.f); ++ } else { ++ this.h = this.b; ++ this.i = MathHelper.e(this.d.a()); ++ } ++ ++ this.h.a(this.g); ++ this.a = new DataBits(this.i, 4096); ++ } ++ } ++ ++ @Override ++ public int onResize(int i, T t0) { ++ this.a(); ++ DataBits databits = this.a; ++ DataPalette datapalette = this.h; ++ ++ this.b(i); ++ ++ int j; ++ ++ for (j = 0; j < databits.b(); ++j) { ++ T t1 = datapalette.a(databits.a(j)); ++ ++ if (t1 != null) { ++ this.setBlockIndex(j, t1); ++ } ++ } ++ ++ j = this.h.a(t0); ++ this.b(); ++ return j; ++ } ++ ++ public T setBlock(int i, int j, int k, T t0) { ++ this.a(); ++ T t1 = this.a(b(i, j, k), t0); ++ ++ this.b(); ++ return t1; ++ } ++ ++ public T b(int i, int j, int k, T t0) { ++ return this.a(b(i, j, k), t0); ++ } ++ ++ protected T a(int i, T t0) { ++ int j = this.h.a(t0); ++ int k = this.a.a(i, j); ++ T t1 = this.h.a(k); ++ ++ return t1 == null ? this.g : t1; ++ } ++ ++ protected void setBlockIndex(int i, T t0) { ++ int j = this.h.a(t0); ++ ++ this.a.b(i, j); ++ } ++ ++ public T a(int i, int j, int k) { ++ return this.a(b(i, j, k)); ++ } ++ ++ protected T a(int i) { ++ T t0 = this.h.a(this.a.a(i)); ++ ++ return t0 == null ? this.g : t0; ++ } ++ ++ public void b(PacketDataSerializer packetdataserializer) { ++ this.a(); ++ packetdataserializer.writeByte(this.i); ++ this.h.b(packetdataserializer); ++ packetdataserializer.a(this.a.a()); ++ this.b(); ++ } ++ ++ public void a(NBTTagList nbttaglist, long[] along) { ++ this.a(); ++ int i = Math.max(4, MathHelper.e(nbttaglist.size())); ++ ++ if (i != this.i) { ++ this.b(i); ++ } ++ ++ this.h.a(nbttaglist); ++ int j = along.length * 64 / 4096; ++ ++ if (this.h == this.b) { ++ DataPalette datapalette = new DataPaletteHash<>(this.d, i, this.c, this.e, this.f); ++ ++ datapalette.a(nbttaglist); ++ DataBits databits = new DataBits(i, 4096, along); ++ ++ for (int k = 0; k < 4096; ++k) { ++ this.a.b(k, this.b.a(datapalette.a(databits.a(k)))); ++ } ++ } else if (j == this.i) { ++ System.arraycopy(along, 0, this.a.a(), 0, along.length); ++ } else { ++ DataBits databits1 = new DataBits(j, 4096, along); ++ ++ for (int l = 0; l < 4096; ++l) { ++ this.a.b(l, databits1.a(l)); ++ } ++ } ++ ++ this.b(); ++ } ++ ++ public void a(NBTTagCompound nbttagcompound, String s, String s1) { ++ this.a(); ++ DataPaletteHash datapalettehash = new DataPaletteHash<>(this.d, this.i, this.c, this.e, this.f); ++ T t0 = this.g; ++ int i = datapalettehash.a(this.g); ++ int[] aint = new int[4096]; ++ ++ for (int j = 0; j < 4096; ++j) { ++ T t1 = this.a(j); ++ ++ if (t1 != t0) { ++ t0 = t1; ++ i = datapalettehash.a(t1); ++ } ++ ++ aint[j] = i; ++ } ++ ++ NBTTagList nbttaglist = new NBTTagList(); ++ ++ datapalettehash.b(nbttaglist); ++ nbttagcompound.set(s, nbttaglist); ++ int k = Math.max(4, MathHelper.e(nbttaglist.size())); ++ DataBits databits = new DataBits(k, 4096); ++ ++ for (int l = 0; l < aint.length; ++l) { ++ databits.b(l, aint[l]); ++ } ++ ++ nbttagcompound.a(s1, databits.a()); ++ this.b(); ++ } ++ ++ public int c() { ++ return 1 + this.h.a() + PacketDataSerializer.a(this.a.b()) + this.a.a().length * 8; ++ } ++ ++ public boolean contains(Predicate predicate) { ++ return this.h.a(predicate); ++ } ++ ++ public void a(DataPaletteBlock.a datapaletteblock_a) { ++ Int2IntOpenHashMap int2intopenhashmap = new Int2IntOpenHashMap(); ++ ++ this.a.a((i) -> { ++ int2intopenhashmap.put(i, int2intopenhashmap.get(i) + 1); ++ }); ++ int2intopenhashmap.int2IntEntrySet().forEach((entry) -> { ++ datapaletteblock_a.accept(this.h.a(entry.getIntKey()), entry.getIntValue()); ++ }); ++ } ++ ++ @FunctionalInterface ++ public interface a { ++ ++ void accept(T t0, int i); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/IChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/IChunkAccess.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2cd04abd72f1135446182ad6294003e526f99a4b +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/IChunkAccess.java +@@ -0,0 +1,148 @@ ++package net.minecraft.world.level.chunk; ++ ++import it.unimi.dsi.fastutil.shorts.ShortArrayList; ++import it.unimi.dsi.fastutil.shorts.ShortList; ++import java.util.Collection; ++import java.util.Map; ++import java.util.Map.Entry; ++import java.util.Set; ++import java.util.stream.Stream; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.TickList; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.levelgen.HeightMap; ++import net.minecraft.world.level.levelgen.feature.StructureGenerator; ++import net.minecraft.world.level.levelgen.structure.StructureStart; ++import net.minecraft.world.level.material.FluidType; ++import org.apache.logging.log4j.LogManager; ++ ++public interface IChunkAccess extends IBlockAccess, IStructureAccess { ++ ++ @Nullable ++ IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag); ++ ++ void setTileEntity(BlockPosition blockposition, TileEntity tileentity); ++ ++ void a(Entity entity); ++ ++ @Nullable ++ default ChunkSection a() { ++ ChunkSection[] achunksection = this.getSections(); ++ ++ for (int i = achunksection.length - 1; i >= 0; --i) { ++ ChunkSection chunksection = achunksection[i]; ++ ++ if (!ChunkSection.a(chunksection)) { ++ return chunksection; ++ } ++ } ++ ++ return null; ++ } ++ ++ default int b() { ++ ChunkSection chunksection = this.a(); ++ ++ return chunksection == null ? 0 : chunksection.getYPosition(); ++ } ++ ++ Set c(); ++ ++ ChunkSection[] getSections(); ++ ++ Collection> f(); ++ ++ void a(HeightMap.Type heightmap_type, long[] along); ++ ++ HeightMap a(HeightMap.Type heightmap_type); ++ ++ int getHighestBlock(HeightMap.Type heightmap_type, int i, int j); ++ ++ ChunkCoordIntPair getPos(); ++ ++ void setLastSaved(long i); ++ ++ Map, StructureStart> h(); ++ ++ void a(Map, StructureStart> map); ++ ++ default boolean a(int i, int j) { ++ if (i < 0) { ++ i = 0; ++ } ++ ++ if (j >= 256) { ++ j = 255; ++ } ++ ++ for (int k = i; k <= j; k += 16) { ++ if (!ChunkSection.a(this.getSections()[k >> 4])) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ @Nullable ++ BiomeStorage getBiomeIndex(); ++ ++ void setNeedsSaving(boolean flag); ++ ++ boolean isNeedsSaving(); ++ ++ ChunkStatus getChunkStatus(); ++ ++ void removeTileEntity(BlockPosition blockposition); ++ ++ default void e(BlockPosition blockposition) { ++ LogManager.getLogger().warn("Trying to mark a block for PostProcessing @ {}, but this operation is not supported.", blockposition); ++ } ++ ++ ShortList[] l(); ++ ++ default void a(short short0, int i) { ++ a(this.l(), i).add(short0); ++ } ++ ++ default void a(NBTTagCompound nbttagcompound) { ++ LogManager.getLogger().warn("Trying to set a BlockEntity, but this operation is not supported."); ++ } ++ ++ @Nullable ++ NBTTagCompound i(BlockPosition blockposition); ++ ++ @Nullable ++ NBTTagCompound j(BlockPosition blockposition); ++ ++ Stream m(); ++ ++ TickList n(); ++ ++ TickList o(); ++ ++ ChunkConverter p(); ++ ++ void setInhabitedTime(long i); ++ ++ long getInhabitedTime(); ++ ++ static ShortList a(ShortList[] ashortlist, int i) { ++ if (ashortlist[i] == null) { ++ ashortlist[i] = new ShortArrayList(); ++ } ++ ++ return ashortlist[i]; ++ } ++ ++ boolean r(); ++ ++ void b(boolean flag); ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/ILightAccess.java b/src/main/java/net/minecraft/world/level/chunk/ILightAccess.java +new file mode 100644 +index 0000000000000000000000000000000000000000..43b8361e8ad0a8c429406cb6ff538020f670bdbd +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/ILightAccess.java +@@ -0,0 +1,16 @@ ++package net.minecraft.world.level.chunk; ++ ++import javax.annotation.Nullable; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.IBlockAccess; ++ ++public interface ILightAccess { ++ ++ @Nullable ++ IBlockAccess c(int i, int j); ++ ++ default void a(EnumSkyBlock enumskyblock, SectionPosition sectionposition) {} ++ ++ IBlockAccess getWorld(); ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4192d30ad2117a12a4058b48581d6cf93b50088c +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -0,0 +1,512 @@ ++package net.minecraft.world.level.chunk; ++ ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import com.google.common.collect.Sets; ++import it.unimi.dsi.fastutil.longs.LongOpenHashSet; ++import it.unimi.dsi.fastutil.longs.LongSet; ++import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; ++import it.unimi.dsi.fastutil.shorts.ShortList; ++import java.util.BitSet; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.EnumSet; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Map.Entry; ++import java.util.Set; ++import java.util.stream.Stream; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.levelgen.HeightMap; ++import net.minecraft.world.level.levelgen.WorldGenStage; ++import net.minecraft.world.level.levelgen.feature.StructureGenerator; ++import net.minecraft.world.level.levelgen.structure.StructureStart; ++import net.minecraft.world.level.lighting.LightEngine; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.level.material.FluidType; ++import net.minecraft.world.level.material.FluidTypes; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class ProtoChunk implements IChunkAccess { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final ChunkCoordIntPair b; ++ private volatile boolean c; ++ @Nullable ++ private BiomeStorage d; ++ @Nullable ++ private volatile LightEngine e; ++ private final Map f; ++ private volatile ChunkStatus g; ++ private final Map h; ++ private final Map i; ++ private final ChunkSection[] j; ++ private final List k; ++ private final List l; ++ private final ShortList[] m; ++ private final Map, StructureStart> n; ++ private final Map, LongSet> o; ++ private final ChunkConverter p; ++ private final ProtoChunkTickList q; ++ private final ProtoChunkTickList r; ++ private long s; ++ private final Map t; ++ private volatile boolean u; ++ ++ public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter) { ++ this(chunkcoordintpair, chunkconverter, (ChunkSection[]) null, new ProtoChunkTickList<>((block) -> { ++ return block == null || block.getBlockData().isAir(); ++ }, chunkcoordintpair), new ProtoChunkTickList<>((fluidtype) -> { ++ return fluidtype == null || fluidtype == FluidTypes.EMPTY; ++ }, chunkcoordintpair)); ++ } ++ ++ public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, @Nullable ChunkSection[] achunksection, ProtoChunkTickList protochunkticklist, ProtoChunkTickList protochunkticklist1) { ++ this.f = Maps.newEnumMap(HeightMap.Type.class); ++ this.g = ChunkStatus.EMPTY; ++ this.h = Maps.newHashMap(); ++ this.i = Maps.newHashMap(); ++ this.j = new ChunkSection[16]; ++ this.k = Lists.newArrayList(); ++ this.l = Lists.newArrayList(); ++ this.m = new ShortList[16]; ++ this.n = Maps.newHashMap(); ++ this.o = Maps.newHashMap(); ++ this.t = new Object2ObjectArrayMap(); ++ this.b = chunkcoordintpair; ++ this.p = chunkconverter; ++ this.q = protochunkticklist; ++ this.r = protochunkticklist1; ++ if (achunksection != null) { ++ if (this.j.length == achunksection.length) { ++ System.arraycopy(achunksection, 0, this.j, 0, this.j.length); ++ } else { ++ ProtoChunk.LOGGER.warn("Could not set level chunk sections, array length is {} instead of {}", achunksection.length, this.j.length); ++ } ++ } ++ ++ } ++ ++ @Override ++ public IBlockData getType(BlockPosition blockposition) { ++ int i = blockposition.getY(); ++ ++ if (World.b(i)) { ++ return Blocks.VOID_AIR.getBlockData(); ++ } else { ++ ChunkSection chunksection = this.getSections()[i >> 4]; ++ ++ return ChunkSection.a(chunksection) ? Blocks.AIR.getBlockData() : chunksection.getType(blockposition.getX() & 15, i & 15, blockposition.getZ() & 15); ++ } ++ } ++ ++ @Override ++ public Fluid getFluid(BlockPosition blockposition) { ++ int i = blockposition.getY(); ++ ++ if (World.b(i)) { ++ return FluidTypes.EMPTY.h(); ++ } else { ++ ChunkSection chunksection = this.getSections()[i >> 4]; ++ ++ return ChunkSection.a(chunksection) ? FluidTypes.EMPTY.h() : chunksection.b(blockposition.getX() & 15, i & 15, blockposition.getZ() & 15); ++ } ++ } ++ ++ @Override ++ public Stream m() { ++ return this.l.stream(); ++ } ++ ++ public ShortList[] w() { ++ ShortList[] ashortlist = new ShortList[16]; ++ Iterator iterator = this.l.iterator(); ++ ++ while (iterator.hasNext()) { ++ BlockPosition blockposition = (BlockPosition) iterator.next(); ++ ++ IChunkAccess.a(ashortlist, blockposition.getY() >> 4).add(l(blockposition)); ++ } ++ ++ return ashortlist; ++ } ++ ++ public void b(short short0, int i) { ++ this.k(a(short0, i, this.b)); ++ } ++ ++ public void k(BlockPosition blockposition) { ++ this.l.add(blockposition.immutableCopy()); ++ } ++ ++ @Nullable ++ @Override ++ public IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag) { ++ int i = blockposition.getX(); ++ int j = blockposition.getY(); ++ int k = blockposition.getZ(); ++ ++ if (j >= 0 && j < 256) { ++ if (this.j[j >> 4] == Chunk.a && iblockdata.a(Blocks.AIR)) { ++ return iblockdata; ++ } else { ++ if (iblockdata.f() > 0) { ++ this.l.add(new BlockPosition((i & 15) + this.getPos().d(), j, (k & 15) + this.getPos().e())); ++ } ++ ++ ChunkSection chunksection = this.a(j >> 4); ++ IBlockData iblockdata1 = chunksection.setType(i & 15, j & 15, k & 15, iblockdata); ++ ++ if (this.g.b(ChunkStatus.FEATURES) && iblockdata != iblockdata1 && (iblockdata.b((IBlockAccess) this, blockposition) != iblockdata1.b((IBlockAccess) this, blockposition) || iblockdata.f() != iblockdata1.f() || iblockdata.e() || iblockdata1.e())) { ++ LightEngine lightengine = this.e(); ++ ++ lightengine.a(blockposition); ++ } ++ ++ EnumSet enumset = this.getChunkStatus().h(); ++ EnumSet enumset1 = null; ++ Iterator iterator = enumset.iterator(); ++ ++ HeightMap.Type heightmap_type; ++ ++ while (iterator.hasNext()) { ++ heightmap_type = (HeightMap.Type) iterator.next(); ++ HeightMap heightmap = (HeightMap) this.f.get(heightmap_type); ++ ++ if (heightmap == null) { ++ if (enumset1 == null) { ++ enumset1 = EnumSet.noneOf(HeightMap.Type.class); ++ } ++ ++ enumset1.add(heightmap_type); ++ } ++ } ++ ++ if (enumset1 != null) { ++ HeightMap.a(this, enumset1); ++ } ++ ++ iterator = enumset.iterator(); ++ ++ while (iterator.hasNext()) { ++ heightmap_type = (HeightMap.Type) iterator.next(); ++ ((HeightMap) this.f.get(heightmap_type)).a(i & 15, j, k & 15, iblockdata); ++ } ++ ++ return iblockdata1; ++ } ++ } else { ++ return Blocks.VOID_AIR.getBlockData(); ++ } ++ } ++ ++ public ChunkSection a(int i) { ++ if (this.j[i] == Chunk.a) { ++ this.j[i] = new ChunkSection(i << 4); ++ } ++ ++ return this.j[i]; ++ } ++ ++ @Override ++ public void setTileEntity(BlockPosition blockposition, TileEntity tileentity) { ++ tileentity.setPosition(blockposition); ++ this.h.put(blockposition, tileentity); ++ } ++ ++ @Override ++ public Set c() { ++ Set set = Sets.newHashSet(this.i.keySet()); ++ ++ set.addAll(this.h.keySet()); ++ return set; ++ } ++ ++ @Nullable ++ @Override ++ public TileEntity getTileEntity(BlockPosition blockposition) { ++ return (TileEntity) this.h.get(blockposition); ++ } ++ ++ public Map x() { ++ return this.h; ++ } ++ ++ public void b(NBTTagCompound nbttagcompound) { ++ this.k.add(nbttagcompound); ++ } ++ ++ @Override ++ public void a(Entity entity) { ++ if (!entity.isPassenger()) { ++ NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ ++ entity.d(nbttagcompound); ++ this.b(nbttagcompound); ++ } ++ } ++ ++ public List y() { ++ return this.k; ++ } ++ ++ public void a(BiomeStorage biomestorage) { ++ this.d = biomestorage; ++ } ++ ++ @Nullable ++ @Override ++ public BiomeStorage getBiomeIndex() { ++ return this.d; ++ } ++ ++ @Override ++ public void setNeedsSaving(boolean flag) { ++ this.c = flag; ++ } ++ ++ @Override ++ public boolean isNeedsSaving() { ++ return this.c; ++ } ++ ++ @Override ++ public ChunkStatus getChunkStatus() { ++ return this.g; ++ } ++ ++ public void a(ChunkStatus chunkstatus) { ++ this.g = chunkstatus; ++ this.setNeedsSaving(true); ++ } ++ ++ @Override ++ public ChunkSection[] getSections() { ++ return this.j; ++ } ++ ++ @Nullable ++ public LightEngine e() { ++ return this.e; ++ } ++ ++ @Override ++ public Collection> f() { ++ return Collections.unmodifiableSet(this.f.entrySet()); ++ } ++ ++ @Override ++ public void a(HeightMap.Type heightmap_type, long[] along) { ++ this.a(heightmap_type).a(along); ++ } ++ ++ @Override ++ public HeightMap a(HeightMap.Type heightmap_type) { ++ return (HeightMap) this.f.computeIfAbsent(heightmap_type, (heightmap_type1) -> { ++ return new HeightMap(this, heightmap_type1); ++ }); ++ } ++ ++ @Override ++ public int getHighestBlock(HeightMap.Type heightmap_type, int i, int j) { ++ HeightMap heightmap = (HeightMap) this.f.get(heightmap_type); ++ ++ if (heightmap == null) { ++ HeightMap.a(this, EnumSet.of(heightmap_type)); ++ heightmap = (HeightMap) this.f.get(heightmap_type); ++ } ++ ++ return heightmap.a(i & 15, j & 15) - 1; ++ } ++ ++ @Override ++ public ChunkCoordIntPair getPos() { ++ return this.b; ++ } ++ ++ @Override ++ public void setLastSaved(long i) {} ++ ++ @Nullable ++ @Override ++ public StructureStart a(StructureGenerator structuregenerator) { ++ return (StructureStart) this.n.get(structuregenerator); ++ } ++ ++ @Override ++ public void a(StructureGenerator structuregenerator, StructureStart structurestart) { ++ this.n.put(structuregenerator, structurestart); ++ this.c = true; ++ } ++ ++ @Override ++ public Map, StructureStart> h() { ++ return Collections.unmodifiableMap(this.n); ++ } ++ ++ @Override ++ public void a(Map, StructureStart> map) { ++ this.n.clear(); ++ this.n.putAll(map); ++ this.c = true; ++ } ++ ++ @Override ++ public LongSet b(StructureGenerator structuregenerator) { ++ return (LongSet) this.o.computeIfAbsent(structuregenerator, (structuregenerator1) -> { ++ return new LongOpenHashSet(); ++ }); ++ } ++ ++ @Override ++ public void a(StructureGenerator structuregenerator, long i) { ++ ((LongSet) this.o.computeIfAbsent(structuregenerator, (structuregenerator1) -> { ++ return new LongOpenHashSet(); ++ })).add(i); ++ this.c = true; ++ } ++ ++ @Override ++ public Map, LongSet> v() { ++ return Collections.unmodifiableMap(this.o); ++ } ++ ++ @Override ++ public void b(Map, LongSet> map) { ++ this.o.clear(); ++ this.o.putAll(map); ++ this.c = true; ++ } ++ ++ public static short l(BlockPosition blockposition) { ++ int i = blockposition.getX(); ++ int j = blockposition.getY(); ++ int k = blockposition.getZ(); ++ int l = i & 15; ++ int i1 = j & 15; ++ int j1 = k & 15; ++ ++ return (short) (l | i1 << 4 | j1 << 8); ++ } ++ ++ public static BlockPosition a(short short0, int i, ChunkCoordIntPair chunkcoordintpair) { ++ int j = (short0 & 15) + (chunkcoordintpair.x << 4); ++ int k = (short0 >>> 4 & 15) + (i << 4); ++ int l = (short0 >>> 8 & 15) + (chunkcoordintpair.z << 4); ++ ++ return new BlockPosition(j, k, l); ++ } ++ ++ @Override ++ public void e(BlockPosition blockposition) { ++ if (!World.isOutsideWorld(blockposition)) { ++ IChunkAccess.a(this.m, blockposition.getY() >> 4).add(l(blockposition)); ++ } ++ ++ } ++ ++ @Override ++ public ShortList[] l() { ++ return this.m; ++ } ++ ++ @Override ++ public void a(short short0, int i) { ++ IChunkAccess.a(this.m, i).add(short0); ++ } ++ ++ @Override ++ public ProtoChunkTickList n() { ++ return this.q; ++ } ++ ++ @Override ++ public ProtoChunkTickList o() { ++ return this.r; ++ } ++ ++ @Override ++ public ChunkConverter p() { ++ return this.p; ++ } ++ ++ @Override ++ public void setInhabitedTime(long i) { ++ this.s = i; ++ } ++ ++ @Override ++ public long getInhabitedTime() { ++ return this.s; ++ } ++ ++ @Override ++ public void a(NBTTagCompound nbttagcompound) { ++ this.i.put(new BlockPosition(nbttagcompound.getInt("x"), nbttagcompound.getInt("y"), nbttagcompound.getInt("z")), nbttagcompound); ++ } ++ ++ public Map z() { ++ return Collections.unmodifiableMap(this.i); ++ } ++ ++ @Override ++ public NBTTagCompound i(BlockPosition blockposition) { ++ return (NBTTagCompound) this.i.get(blockposition); ++ } ++ ++ @Nullable ++ @Override ++ public NBTTagCompound j(BlockPosition blockposition) { ++ TileEntity tileentity = this.getTileEntity(blockposition); ++ ++ return tileentity != null ? tileentity.save(new NBTTagCompound()) : (NBTTagCompound) this.i.get(blockposition); ++ } ++ ++ @Override ++ public void removeTileEntity(BlockPosition blockposition) { ++ this.h.remove(blockposition); ++ this.i.remove(blockposition); ++ } ++ ++ @Nullable ++ public BitSet a(WorldGenStage.Features worldgenstage_features) { ++ return (BitSet) this.t.get(worldgenstage_features); ++ } ++ ++ public BitSet b(WorldGenStage.Features worldgenstage_features) { ++ return (BitSet) this.t.computeIfAbsent(worldgenstage_features, (worldgenstage_features1) -> { ++ return new BitSet(65536); ++ }); ++ } ++ ++ public void a(WorldGenStage.Features worldgenstage_features, BitSet bitset) { ++ this.t.put(worldgenstage_features, bitset); ++ } ++ ++ public void a(LightEngine lightengine) { ++ this.e = lightengine; ++ } ++ ++ @Override ++ public boolean r() { ++ return this.u; ++ } ++ ++ @Override ++ public void b(boolean flag) { ++ this.u = flag; ++ this.setNeedsSaving(true); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunkExtension.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunkExtension.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c059d3d055c35b492680556e8605966e2caaf7fd +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunkExtension.java +@@ -0,0 +1,219 @@ ++package net.minecraft.world.level.chunk; ++ ++import it.unimi.dsi.fastutil.longs.LongSet; ++import java.util.BitSet; ++import java.util.Map; ++import java.util.stream.Stream; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.levelgen.HeightMap; ++import net.minecraft.world.level.levelgen.WorldGenStage; ++import net.minecraft.world.level.levelgen.feature.StructureGenerator; ++import net.minecraft.world.level.levelgen.structure.StructureStart; ++import net.minecraft.world.level.lighting.LightEngine; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.level.material.FluidType; ++import net.minecraft.world.level.material.FluidTypes; ++ ++public class ProtoChunkExtension extends ProtoChunk { ++ ++ private final Chunk a; ++ ++ public ProtoChunkExtension(Chunk chunk) { ++ super(chunk.getPos(), ChunkConverter.a); ++ this.a = chunk; ++ } ++ ++ @Nullable ++ @Override ++ public TileEntity getTileEntity(BlockPosition blockposition) { ++ return this.a.getTileEntity(blockposition); ++ } ++ ++ @Nullable ++ @Override ++ public IBlockData getType(BlockPosition blockposition) { ++ return this.a.getType(blockposition); ++ } ++ ++ @Override ++ public Fluid getFluid(BlockPosition blockposition) { ++ return this.a.getFluid(blockposition); ++ } ++ ++ @Override ++ public int K() { ++ return this.a.K(); ++ } ++ ++ @Nullable ++ @Override ++ public IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag) { ++ return null; ++ } ++ ++ @Override ++ public void setTileEntity(BlockPosition blockposition, TileEntity tileentity) {} ++ ++ @Override ++ public void a(Entity entity) {} ++ ++ @Override ++ public void a(ChunkStatus chunkstatus) {} ++ ++ @Override ++ public ChunkSection[] getSections() { ++ return this.a.getSections(); ++ } ++ ++ @Nullable ++ @Override ++ public LightEngine e() { ++ return this.a.e(); ++ } ++ ++ @Override ++ public void a(HeightMap.Type heightmap_type, long[] along) {} ++ ++ private HeightMap.Type c(HeightMap.Type heightmap_type) { ++ return heightmap_type == HeightMap.Type.WORLD_SURFACE_WG ? HeightMap.Type.WORLD_SURFACE : (heightmap_type == HeightMap.Type.OCEAN_FLOOR_WG ? HeightMap.Type.OCEAN_FLOOR : heightmap_type); ++ } ++ ++ @Override ++ public int getHighestBlock(HeightMap.Type heightmap_type, int i, int j) { ++ return this.a.getHighestBlock(this.c(heightmap_type), i, j); ++ } ++ ++ @Override ++ public ChunkCoordIntPair getPos() { ++ return this.a.getPos(); ++ } ++ ++ @Override ++ public void setLastSaved(long i) {} ++ ++ @Nullable ++ @Override ++ public StructureStart a(StructureGenerator structuregenerator) { ++ return this.a.a(structuregenerator); ++ } ++ ++ @Override ++ public void a(StructureGenerator structuregenerator, StructureStart structurestart) {} ++ ++ @Override ++ public Map, StructureStart> h() { ++ return this.a.h(); ++ } ++ ++ @Override ++ public void a(Map, StructureStart> map) {} ++ ++ @Override ++ public LongSet b(StructureGenerator structuregenerator) { ++ return this.a.b(structuregenerator); ++ } ++ ++ @Override ++ public void a(StructureGenerator structuregenerator, long i) {} ++ ++ @Override ++ public Map, LongSet> v() { ++ return this.a.v(); ++ } ++ ++ @Override ++ public void b(Map, LongSet> map) {} ++ ++ @Override ++ public BiomeStorage getBiomeIndex() { ++ return this.a.getBiomeIndex(); ++ } ++ ++ @Override ++ public void setNeedsSaving(boolean flag) {} ++ ++ @Override ++ public boolean isNeedsSaving() { ++ return false; ++ } ++ ++ @Override ++ public ChunkStatus getChunkStatus() { ++ return this.a.getChunkStatus(); ++ } ++ ++ @Override ++ public void removeTileEntity(BlockPosition blockposition) {} ++ ++ @Override ++ public void e(BlockPosition blockposition) {} ++ ++ @Override ++ public void a(NBTTagCompound nbttagcompound) {} ++ ++ @Nullable ++ @Override ++ public NBTTagCompound i(BlockPosition blockposition) { ++ return this.a.i(blockposition); ++ } ++ ++ @Nullable ++ @Override ++ public NBTTagCompound j(BlockPosition blockposition) { ++ return this.a.j(blockposition); ++ } ++ ++ @Override ++ public void a(BiomeStorage biomestorage) {} ++ ++ @Override ++ public Stream m() { ++ return this.a.m(); ++ } ++ ++ @Override ++ public ProtoChunkTickList n() { ++ return new ProtoChunkTickList<>((block) -> { ++ return block.getBlockData().isAir(); ++ }, this.getPos()); ++ } ++ ++ @Override ++ public ProtoChunkTickList o() { ++ return new ProtoChunkTickList<>((fluidtype) -> { ++ return fluidtype == FluidTypes.EMPTY; ++ }, this.getPos()); ++ } ++ ++ @Override ++ public BitSet a(WorldGenStage.Features worldgenstage_features) { ++ throw (UnsupportedOperationException) SystemUtils.c((Throwable) (new UnsupportedOperationException("Meaningless in this context"))); ++ } ++ ++ @Override ++ public BitSet b(WorldGenStage.Features worldgenstage_features) { ++ throw (UnsupportedOperationException) SystemUtils.c((Throwable) (new UnsupportedOperationException("Meaningless in this context"))); ++ } ++ ++ public Chunk u() { ++ return this.a; ++ } ++ ++ @Override ++ public boolean r() { ++ return this.a.r(); ++ } ++ ++ @Override ++ public void b(boolean flag) { ++ this.a.b(flag); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..88c2643a18165bd7a9e6e056b926d6e894ff60d4 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java +@@ -0,0 +1,187 @@ ++package net.minecraft.world.level.chunk.storage; ++ ++import com.google.common.collect.Maps; ++import com.mojang.datafixers.util.Either; ++import java.io.File; ++import java.io.IOException; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Map.Entry; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.CompletionException; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.function.Function; ++import java.util.function.Supplier; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.util.Unit; ++import net.minecraft.util.thread.PairedQueue; ++import net.minecraft.util.thread.ThreadedMailbox; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class IOWorker implements AutoCloseable { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final AtomicBoolean b = new AtomicBoolean(); ++ private final ThreadedMailbox c; ++ private final RegionFileCache d; ++ private final Map e = Maps.newLinkedHashMap(); ++ ++ protected IOWorker(File file, boolean flag, String s) { ++ this.d = new RegionFileCache(file, flag); ++ this.c = new ThreadedMailbox<>(new PairedQueue.a(IOWorker.Priority.values().length), SystemUtils.g(), "IOWorker-" + s); ++ } ++ ++ public CompletableFuture a(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) { ++ return this.a(() -> { ++ IOWorker.a ioworker_a = (IOWorker.a) this.e.computeIfAbsent(chunkcoordintpair, (chunkcoordintpair1) -> { ++ return new IOWorker.a(nbttagcompound); ++ }); ++ ++ ioworker_a.a = nbttagcompound; ++ return Either.left(ioworker_a.b); ++ }).thenCompose(Function.identity()); ++ } ++ ++ @Nullable ++ public NBTTagCompound a(ChunkCoordIntPair chunkcoordintpair) throws IOException { ++ CompletableFuture completablefuture = this.a(() -> { ++ IOWorker.a ioworker_a = (IOWorker.a) this.e.get(chunkcoordintpair); ++ ++ if (ioworker_a != null) { ++ return Either.left(ioworker_a.a); ++ } else { ++ try { ++ NBTTagCompound nbttagcompound = this.d.read(chunkcoordintpair); ++ ++ return Either.left(nbttagcompound); ++ } catch (Exception exception) { ++ IOWorker.LOGGER.warn("Failed to read chunk {}", chunkcoordintpair, exception); ++ return Either.right(exception); ++ } ++ } ++ }); ++ ++ try { ++ return (NBTTagCompound) completablefuture.join(); ++ } catch (CompletionException completionexception) { ++ if (completionexception.getCause() instanceof IOException) { ++ throw (IOException) completionexception.getCause(); ++ } else { ++ throw completionexception; ++ } ++ } ++ } ++ ++ public CompletableFuture a() { ++ CompletableFuture completablefuture = this.a(() -> { ++ return Either.left(CompletableFuture.allOf((CompletableFuture[]) this.e.values().stream().map((ioworker_a) -> { ++ return ioworker_a.b; ++ }).toArray((i) -> { ++ return new CompletableFuture[i]; ++ }))); ++ }).thenCompose(Function.identity()); ++ ++ return completablefuture.thenCompose((ovoid) -> { ++ return this.a(() -> { ++ try { ++ this.d.a(); ++ return Either.left((Object) null); ++ } catch (Exception exception) { ++ IOWorker.LOGGER.warn("Failed to synchronized chunks", exception); ++ return Either.right(exception); ++ } ++ }); ++ }); ++ } ++ ++ private CompletableFuture a(Supplier> supplier) { ++ return this.c.c((mailbox) -> { ++ return new PairedQueue.b(IOWorker.Priority.HIGH.ordinal(), () -> { ++ if (!this.b.get()) { ++ mailbox.a(supplier.get()); ++ } ++ ++ this.c(); ++ }); ++ }); ++ } ++ ++ private void b() { ++ Iterator> iterator = this.e.entrySet().iterator(); ++ ++ if (iterator.hasNext()) { ++ Entry entry = (Entry) iterator.next(); ++ ++ iterator.remove(); ++ this.a((ChunkCoordIntPair) entry.getKey(), (IOWorker.a) entry.getValue()); ++ this.c(); ++ } ++ } ++ ++ private void c() { ++ this.c.a((Object) (new PairedQueue.b(IOWorker.Priority.LOW.ordinal(), this::b))); ++ } ++ ++ private void a(ChunkCoordIntPair chunkcoordintpair, IOWorker.a ioworker_a) { ++ try { ++ this.d.write(chunkcoordintpair, ioworker_a.a); ++ ioworker_a.b.complete((Object) null); ++ } catch (Exception exception) { ++ IOWorker.LOGGER.error("Failed to store chunk {}", chunkcoordintpair, exception); ++ ioworker_a.b.completeExceptionally(exception); ++ } ++ ++ } ++ ++ public void close() throws IOException { ++ if (this.b.compareAndSet(false, true)) { ++ CompletableFuture completablefuture = this.c.b((mailbox) -> { ++ return new PairedQueue.b(IOWorker.Priority.HIGH.ordinal(), () -> { ++ mailbox.a(Unit.INSTANCE); ++ }); ++ }); ++ ++ try { ++ completablefuture.join(); ++ } catch (CompletionException completionexception) { ++ if (completionexception.getCause() instanceof IOException) { ++ throw (IOException) completionexception.getCause(); ++ } ++ ++ throw completionexception; ++ } ++ ++ this.c.close(); ++ this.e.forEach(this::a); ++ this.e.clear(); ++ ++ try { ++ this.d.close(); ++ } catch (Exception exception) { ++ IOWorker.LOGGER.error("Failed to close storage", exception); ++ } ++ ++ } ++ } ++ ++ static class a { ++ ++ private NBTTagCompound a; ++ private final CompletableFuture b = new CompletableFuture(); ++ ++ public a(NBTTagCompound nbttagcompound) { ++ this.a = nbttagcompound; ++ } ++ } ++ ++ static enum Priority { ++ ++ HIGH, LOW; ++ ++ private Priority() {} ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileBitSet.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileBitSet.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c96eac4b0b519b2807153fa5a8ebf5a020a2b140 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileBitSet.java +@@ -0,0 +1,34 @@ ++package net.minecraft.world.level.chunk.storage; ++ ++import java.util.BitSet; ++ ++public class RegionFileBitSet { ++ ++ private final BitSet a = new BitSet(); ++ ++ public RegionFileBitSet() {} ++ ++ public void a(int i, int j) { ++ this.a.set(i, i + j); ++ } ++ ++ public void b(int i, int j) { ++ this.a.clear(i, i + j); ++ } ++ ++ public int a(int i) { ++ int j = 0; ++ ++ while (true) { ++ int k = this.a.nextClearBit(j); ++ int l = this.a.nextSetBit(k); ++ ++ if (l == -1 || l - k >= i) { ++ this.a(k, i); ++ return k; ++ } ++ ++ j = l; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCompression.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCompression.java +new file mode 100644 +index 0000000000000000000000000000000000000000..78728e064479e7056b7c69e306854330691faa12 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCompression.java +@@ -0,0 +1,65 @@ ++package net.minecraft.world.level.chunk.storage; ++ ++import it.unimi.dsi.fastutil.ints.Int2ObjectMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++import java.util.zip.DeflaterOutputStream; ++import java.util.zip.GZIPInputStream; ++import java.util.zip.GZIPOutputStream; ++import java.util.zip.InflaterInputStream; ++import javax.annotation.Nullable; ++ ++public class RegionFileCompression { ++ ++ private static final Int2ObjectMap d = new Int2ObjectOpenHashMap(); ++ public static final RegionFileCompression a = a(new RegionFileCompression(1, GZIPInputStream::new, GZIPOutputStream::new)); ++ public static final RegionFileCompression b = a(new RegionFileCompression(2, InflaterInputStream::new, DeflaterOutputStream::new)); ++ public static final RegionFileCompression c = a(new RegionFileCompression(3, (inputstream) -> { ++ return inputstream; ++ }, (outputstream) -> { ++ return outputstream; ++ })); ++ private final int e; ++ private final RegionFileCompression.a f; ++ private final RegionFileCompression.a g; ++ ++ private RegionFileCompression(int i, RegionFileCompression.a regionfilecompression_a, RegionFileCompression.a regionfilecompression_a1) { ++ this.e = i; ++ this.f = regionfilecompression_a; ++ this.g = regionfilecompression_a1; ++ } ++ ++ private static RegionFileCompression a(RegionFileCompression regionfilecompression) { ++ RegionFileCompression.d.put(regionfilecompression.e, regionfilecompression); ++ return regionfilecompression; ++ } ++ ++ @Nullable ++ public static RegionFileCompression a(int i) { ++ return (RegionFileCompression) RegionFileCompression.d.get(i); ++ } ++ ++ public static boolean b(int i) { ++ return RegionFileCompression.d.containsKey(i); ++ } ++ ++ public int a() { ++ return this.e; ++ } ++ ++ public OutputStream a(OutputStream outputstream) throws IOException { ++ return (OutputStream) this.g.wrap(outputstream); ++ } ++ ++ public InputStream a(InputStream inputstream) throws IOException { ++ return (InputStream) this.f.wrap(inputstream); ++ } ++ ++ @FunctionalInterface ++ interface a { ++ ++ O wrap(O o0) throws IOException; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8ad97a8a2189553da88810380b1c240079eacc93 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java +@@ -0,0 +1,230 @@ ++package net.minecraft.world.level.chunk.storage; ++ ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Maps; ++import com.mojang.datafixers.DataFixer; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.DataResult; ++import com.mojang.serialization.Dynamic; ++import com.mojang.serialization.DynamicOps; ++import com.mojang.serialization.OptionalDynamic; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; ++import java.io.File; ++import java.io.IOException; ++import java.util.Map; ++import java.util.Optional; ++import java.util.function.BooleanSupplier; ++import java.util.function.Function; ++import javax.annotation.Nullable; ++import net.minecraft.SharedConstants; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.nbt.DynamicOpsNBT; ++import net.minecraft.nbt.NBTBase; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.util.datafix.DataFixTypes; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.World; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class RegionFileSection implements AutoCloseable { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private final IOWorker b; ++ private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); ++ private final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); ++ private final Function> e; ++ private final Function f; ++ private final DataFixer g; ++ private final DataFixTypes h; ++ ++ public RegionFileSection(File file, Function> function, Function function1, DataFixer datafixer, DataFixTypes datafixtypes, boolean flag) { ++ this.e = function; ++ this.f = function1; ++ this.g = datafixer; ++ this.h = datafixtypes; ++ this.b = new IOWorker(file, flag, file.getName()); ++ } ++ ++ protected void a(BooleanSupplier booleansupplier) { ++ while (!this.d.isEmpty() && booleansupplier.getAsBoolean()) { ++ ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(this.d.firstLong()).r(); ++ ++ this.d(chunkcoordintpair); ++ } ++ ++ } ++ ++ @Nullable ++ protected Optional c(long i) { ++ return (Optional) this.c.get(i); ++ } ++ ++ protected Optional d(long i) { ++ SectionPosition sectionposition = SectionPosition.a(i); ++ ++ if (this.b(sectionposition)) { ++ return Optional.empty(); ++ } else { ++ Optional optional = this.c(i); ++ ++ if (optional != null) { ++ return optional; ++ } else { ++ this.b(sectionposition.r()); ++ optional = this.c(i); ++ if (optional == null) { ++ throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException())); ++ } else { ++ return optional; ++ } ++ } ++ } ++ } ++ ++ protected boolean b(SectionPosition sectionposition) { ++ return World.b(SectionPosition.c(sectionposition.b())); ++ } ++ ++ protected R e(long i) { ++ Optional optional = this.d(i); ++ ++ if (optional.isPresent()) { ++ return optional.get(); ++ } else { ++ R r0 = this.f.apply(() -> { ++ this.a(i); ++ }); ++ ++ this.c.put(i, Optional.of(r0)); ++ return r0; ++ } ++ } ++ ++ private void b(ChunkCoordIntPair chunkcoordintpair) { ++ this.a(chunkcoordintpair, DynamicOpsNBT.a, this.c(chunkcoordintpair)); ++ } ++ ++ @Nullable ++ private NBTTagCompound c(ChunkCoordIntPair chunkcoordintpair) { ++ try { ++ return this.b.a(chunkcoordintpair); ++ } catch (IOException ioexception) { ++ RegionFileSection.LOGGER.error("Error reading chunk {} data from disk", chunkcoordintpair, ioexception); ++ return null; ++ } ++ } ++ ++ private void a(ChunkCoordIntPair chunkcoordintpair, DynamicOps dynamicops, @Nullable T t0) { ++ if (t0 == null) { ++ for (int i = 0; i < 16; ++i) { ++ this.c.put(SectionPosition.a(chunkcoordintpair, i).s(), Optional.empty()); ++ } ++ } else { ++ Dynamic dynamic = new Dynamic(dynamicops, t0); ++ int j = a(dynamic); ++ int k = SharedConstants.getGameVersion().getWorldVersion(); ++ boolean flag = j != k; ++ Dynamic dynamic1 = this.g.update(this.h.a(), dynamic, j, k); ++ OptionalDynamic optionaldynamic = dynamic1.get("Sections"); ++ ++ for (int l = 0; l < 16; ++l) { ++ long i1 = SectionPosition.a(chunkcoordintpair, l).s(); ++ Optional optional = optionaldynamic.get(Integer.toString(l)).result().flatMap((dynamic2) -> { ++ DataResult dataresult = ((Codec) this.e.apply(() -> { ++ this.a(i1); ++ })).parse(dynamic2); ++ Logger logger = RegionFileSection.LOGGER; ++ ++ logger.getClass(); ++ return dataresult.resultOrPartial(logger::error); ++ }); ++ ++ this.c.put(i1, optional); ++ optional.ifPresent((object) -> { ++ this.b(i1); ++ if (flag) { ++ this.a(i1); ++ } ++ ++ }); ++ } ++ } ++ ++ } ++ ++ private void d(ChunkCoordIntPair chunkcoordintpair) { ++ Dynamic dynamic = this.a(chunkcoordintpair, DynamicOpsNBT.a); ++ NBTBase nbtbase = (NBTBase) dynamic.getValue(); ++ ++ if (nbtbase instanceof NBTTagCompound) { ++ this.b.a(chunkcoordintpair, (NBTTagCompound) nbtbase); ++ } else { ++ RegionFileSection.LOGGER.error("Expected compound tag, got {}", nbtbase); ++ } ++ ++ } ++ ++ private Dynamic a(ChunkCoordIntPair chunkcoordintpair, DynamicOps dynamicops) { ++ Map map = Maps.newHashMap(); ++ ++ for (int i = 0; i < 16; ++i) { ++ long j = SectionPosition.a(chunkcoordintpair, i).s(); ++ ++ this.d.remove(j); ++ Optional optional = (Optional) this.c.get(j); ++ ++ if (optional != null && optional.isPresent()) { ++ DataResult dataresult = ((Codec) this.e.apply(() -> { ++ this.a(j); ++ })).encodeStart(dynamicops, optional.get()); ++ String s = Integer.toString(i); ++ Logger logger = RegionFileSection.LOGGER; ++ ++ logger.getClass(); ++ dataresult.resultOrPartial(logger::error).ifPresent((object) -> { ++ map.put(dynamicops.createString(s), object); ++ }); ++ } ++ } ++ ++ return new Dynamic(dynamicops, dynamicops.createMap(ImmutableMap.of(dynamicops.createString("Sections"), dynamicops.createMap(map), dynamicops.createString("DataVersion"), dynamicops.createInt(SharedConstants.getGameVersion().getWorldVersion())))); ++ } ++ ++ protected void b(long i) {} ++ ++ protected void a(long i) { ++ Optional optional = (Optional) this.c.get(i); ++ ++ if (optional != null && optional.isPresent()) { ++ this.d.add(i); ++ } else { ++ RegionFileSection.LOGGER.warn("No data for position: {}", SectionPosition.a(i)); ++ } ++ } ++ ++ private static int a(Dynamic dynamic) { ++ return dynamic.get("DataVersion").asInt(1945); ++ } ++ ++ public void a(ChunkCoordIntPair chunkcoordintpair) { ++ if (!this.d.isEmpty()) { ++ for (int i = 0; i < 16; ++i) { ++ long j = SectionPosition.a(chunkcoordintpair, i).s(); ++ ++ if (this.d.contains(j)) { ++ this.d(chunkcoordintpair); ++ return; ++ } ++ } ++ } ++ ++ } ++ ++ public void close() throws IOException { ++ this.b.close(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/dimension/DimensionManager.java b/src/main/java/net/minecraft/world/level/dimension/DimensionManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..72bc1a1e1c2153550313e93cf7df901a514a9bef +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/dimension/DimensionManager.java +@@ -0,0 +1,268 @@ ++package net.minecraft.world.level.dimension; ++ ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.DataResult; ++import com.mojang.serialization.Dynamic; ++import com.mojang.serialization.Lifecycle; ++import com.mojang.serialization.codecs.RecordCodecBuilder; ++import java.io.File; ++import java.util.Optional; ++import java.util.OptionalLong; ++import java.util.function.Supplier; ++import net.minecraft.core.IRegistry; ++import net.minecraft.core.IRegistryCustom; ++import net.minecraft.core.IRegistryWritable; ++import net.minecraft.core.RegistryMaterials; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.resources.RegistryFileCodec; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsBlock; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.biome.BiomeBase; ++import net.minecraft.world.level.biome.GenLayerZoomVoronoi; ++import net.minecraft.world.level.biome.GenLayerZoomVoronoiFixed; ++import net.minecraft.world.level.biome.GenLayerZoomer; ++import net.minecraft.world.level.biome.WorldChunkManagerMultiNoise; ++import net.minecraft.world.level.biome.WorldChunkManagerTheEnd; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.chunk.ChunkGenerator; ++import net.minecraft.world.level.levelgen.ChunkGeneratorAbstract; ++import net.minecraft.world.level.levelgen.GeneratorSettingBase; ++ ++public class DimensionManager { ++ ++ public static final MinecraftKey OVERWORLD_KEY = new MinecraftKey("overworld"); ++ public static final MinecraftKey THE_NETHER_KEY = new MinecraftKey("the_nether"); ++ public static final MinecraftKey THE_END_KEY = new MinecraftKey("the_end"); ++ public static final Codec d = RecordCodecBuilder.create((instance) -> { ++ return instance.group(Codec.LONG.optionalFieldOf("fixed_time").xmap((optional) -> { ++ return (OptionalLong) optional.map(OptionalLong::of).orElseGet(OptionalLong::empty); ++ }, (optionallong) -> { ++ return optionallong.isPresent() ? Optional.of(optionallong.getAsLong()) : Optional.empty(); ++ }).forGetter((dimensionmanager) -> { ++ return dimensionmanager.fixedTime; ++ }), Codec.BOOL.fieldOf("has_skylight").forGetter(DimensionManager::hasSkyLight), Codec.BOOL.fieldOf("has_ceiling").forGetter(DimensionManager::hasCeiling), Codec.BOOL.fieldOf("ultrawarm").forGetter(DimensionManager::isNether), Codec.BOOL.fieldOf("natural").forGetter(DimensionManager::isNatural), Codec.doubleRange(9.999999747378752E-6D, 3.0E7D).fieldOf("coordinate_scale").forGetter(DimensionManager::getCoordinateScale), Codec.BOOL.fieldOf("piglin_safe").forGetter(DimensionManager::isPiglinSafe), Codec.BOOL.fieldOf("bed_works").forGetter(DimensionManager::isBedWorks), Codec.BOOL.fieldOf("respawn_anchor_works").forGetter(DimensionManager::isRespawnAnchorWorks), Codec.BOOL.fieldOf("has_raids").forGetter(DimensionManager::hasRaids), Codec.intRange(0, 256).fieldOf("logical_height").forGetter(DimensionManager::getLogicalHeight), MinecraftKey.a.fieldOf("infiniburn").forGetter((dimensionmanager) -> { ++ return dimensionmanager.infiniburn; ++ }), MinecraftKey.a.fieldOf("effects").orElse(DimensionManager.OVERWORLD_KEY).forGetter((dimensionmanager) -> { ++ return dimensionmanager.effects; ++ }), Codec.FLOAT.fieldOf("ambient_light").forGetter((dimensionmanager) -> { ++ return dimensionmanager.ambientLight; ++ })).apply(instance, DimensionManager::new); ++ }); ++ public static final float[] e = new float[]{1.0F, 0.75F, 0.5F, 0.25F, 0.0F, 0.25F, 0.5F, 0.75F}; ++ public static final ResourceKey OVERWORLD = ResourceKey.a(IRegistry.K, new MinecraftKey("overworld")); ++ public static final ResourceKey THE_NETHER = ResourceKey.a(IRegistry.K, new MinecraftKey("the_nether")); ++ public static final ResourceKey THE_END = ResourceKey.a(IRegistry.K, new MinecraftKey("the_end")); ++ protected static final DimensionManager OVERWORLD_IMPL = new DimensionManager(OptionalLong.empty(), true, false, false, true, 1.0D, false, false, true, false, true, 256, GenLayerZoomVoronoiFixed.INSTANCE, TagsBlock.aE.a(), DimensionManager.OVERWORLD_KEY, 0.0F); ++ protected static final DimensionManager THE_NETHER_IMPL = new DimensionManager(OptionalLong.of(18000L), false, true, true, false, 8.0D, false, true, false, true, false, 128, GenLayerZoomVoronoi.INSTANCE, TagsBlock.aF.a(), DimensionManager.THE_NETHER_KEY, 0.1F); ++ protected static final DimensionManager THE_END_IMPL = new DimensionManager(OptionalLong.of(6000L), false, false, false, false, 1.0D, true, false, false, false, true, 256, GenLayerZoomVoronoi.INSTANCE, TagsBlock.aG.a(), DimensionManager.THE_END_KEY, 0.0F); ++ public static final ResourceKey l = ResourceKey.a(IRegistry.K, new MinecraftKey("overworld_caves")); ++ protected static final DimensionManager m = new DimensionManager(OptionalLong.empty(), true, true, false, true, 1.0D, false, false, true, false, true, 256, GenLayerZoomVoronoiFixed.INSTANCE, TagsBlock.aE.a(), DimensionManager.OVERWORLD_KEY, 0.0F); ++ public static final Codec> n = RegistryFileCodec.a(IRegistry.K, DimensionManager.d); ++ private final OptionalLong fixedTime; ++ private final boolean hasSkylight; ++ private final boolean hasCeiling; ++ private final boolean ultraWarm; ++ private final boolean natural; ++ private final double coordinateScale; ++ private final boolean createDragonBattle; ++ private final boolean piglinSafe; ++ private final boolean bedWorks; ++ private final boolean respawnAnchorWorks; ++ private final boolean hasRaids; ++ private final int logicalHeight; ++ private final GenLayerZoomer genLayerZoomer; ++ private final MinecraftKey infiniburn; ++ private final MinecraftKey effects; ++ private final float ambientLight; ++ private final transient float[] E; ++ ++ protected DimensionManager(OptionalLong optionallong, boolean flag, boolean flag1, boolean flag2, boolean flag3, double d0, boolean flag4, boolean flag5, boolean flag6, boolean flag7, int i, MinecraftKey minecraftkey, MinecraftKey minecraftkey1, float f) { ++ this(optionallong, flag, flag1, flag2, flag3, d0, false, flag4, flag5, flag6, flag7, i, GenLayerZoomVoronoi.INSTANCE, minecraftkey, minecraftkey1, f); ++ } ++ ++ protected DimensionManager(OptionalLong optionallong, boolean flag, boolean flag1, boolean flag2, boolean flag3, double d0, boolean flag4, boolean flag5, boolean flag6, boolean flag7, boolean flag8, int i, GenLayerZoomer genlayerzoomer, MinecraftKey minecraftkey, MinecraftKey minecraftkey1, float f) { ++ this.fixedTime = optionallong; ++ this.hasSkylight = flag; ++ this.hasCeiling = flag1; ++ this.ultraWarm = flag2; ++ this.natural = flag3; ++ this.coordinateScale = d0; ++ this.createDragonBattle = flag4; ++ this.piglinSafe = flag5; ++ this.bedWorks = flag6; ++ this.respawnAnchorWorks = flag7; ++ this.hasRaids = flag8; ++ this.logicalHeight = i; ++ this.genLayerZoomer = genlayerzoomer; ++ this.infiniburn = minecraftkey; ++ this.effects = minecraftkey1; ++ this.ambientLight = f; ++ this.E = a(f); ++ } ++ ++ private static float[] a(float f) { ++ float[] afloat = new float[16]; ++ ++ for (int i = 0; i <= 15; ++i) { ++ float f1 = (float) i / 15.0F; ++ float f2 = f1 / (4.0F - 3.0F * f1); ++ ++ afloat[i] = MathHelper.g(f, f2, 1.0F); ++ } ++ ++ return afloat; ++ } ++ ++ @Deprecated ++ public static DataResult> a(Dynamic dynamic) { ++ Optional optional = dynamic.asNumber().result(); ++ ++ if (optional.isPresent()) { ++ int i = ((Number) optional.get()).intValue(); ++ ++ if (i == -1) { ++ return DataResult.success(World.THE_NETHER); ++ } ++ ++ if (i == 0) { ++ return DataResult.success(World.OVERWORLD); ++ } ++ ++ if (i == 1) { ++ return DataResult.success(World.THE_END); ++ } ++ } ++ ++ return World.f.parse(dynamic); ++ } ++ ++ public static IRegistryCustom.Dimension a(IRegistryCustom.Dimension iregistrycustom_dimension) { ++ IRegistryWritable iregistrywritable = iregistrycustom_dimension.b(IRegistry.K); ++ ++ iregistrywritable.a(DimensionManager.OVERWORLD, (Object) DimensionManager.OVERWORLD_IMPL, Lifecycle.stable()); ++ iregistrywritable.a(DimensionManager.l, (Object) DimensionManager.m, Lifecycle.stable()); ++ iregistrywritable.a(DimensionManager.THE_NETHER, (Object) DimensionManager.THE_NETHER_IMPL, Lifecycle.stable()); ++ iregistrywritable.a(DimensionManager.THE_END, (Object) DimensionManager.THE_END_IMPL, Lifecycle.stable()); ++ return iregistrycustom_dimension; ++ } ++ ++ private static ChunkGenerator a(IRegistry iregistry, IRegistry iregistry1, long i) { ++ return new ChunkGeneratorAbstract(new WorldChunkManagerTheEnd(iregistry, i), i, () -> { ++ return (GeneratorSettingBase) iregistry1.d(GeneratorSettingBase.f); ++ }); ++ } ++ ++ private static ChunkGenerator b(IRegistry iregistry, IRegistry iregistry1, long i) { ++ return new ChunkGeneratorAbstract(WorldChunkManagerMultiNoise.b.a.a(iregistry, i), i, () -> { ++ return (GeneratorSettingBase) iregistry1.d(GeneratorSettingBase.e); ++ }); ++ } ++ ++ public static RegistryMaterials a(IRegistry iregistry, IRegistry iregistry1, IRegistry iregistry2, long i) { ++ RegistryMaterials registrymaterials = new RegistryMaterials<>(IRegistry.M, Lifecycle.experimental()); ++ ++ registrymaterials.a(WorldDimension.THE_NETHER, (Object) (new WorldDimension(() -> { ++ return (DimensionManager) iregistry.d(DimensionManager.THE_NETHER); ++ }, b(iregistry1, iregistry2, i))), Lifecycle.stable()); ++ registrymaterials.a(WorldDimension.THE_END, (Object) (new WorldDimension(() -> { ++ return (DimensionManager) iregistry.d(DimensionManager.THE_END); ++ }, a(iregistry1, iregistry2, i))), Lifecycle.stable()); ++ return registrymaterials; ++ } ++ ++ public static double a(DimensionManager dimensionmanager, DimensionManager dimensionmanager1) { ++ double d0 = dimensionmanager.getCoordinateScale(); ++ double d1 = dimensionmanager1.getCoordinateScale(); ++ ++ return d0 / d1; ++ } ++ ++ @Deprecated ++ public String getSuffix() { ++ return this.a(DimensionManager.THE_END_IMPL) ? "_end" : ""; ++ } ++ ++ public static File a(ResourceKey resourcekey, File file) { ++ return resourcekey == World.OVERWORLD ? file : (resourcekey == World.THE_END ? new File(file, "DIM1") : (resourcekey == World.THE_NETHER ? new File(file, "DIM-1") : new File(file, "dimensions/" + resourcekey.a().getNamespace() + "/" + resourcekey.a().getKey()))); ++ } ++ ++ public boolean hasSkyLight() { ++ return this.hasSkylight; ++ } ++ ++ public boolean hasCeiling() { ++ return this.hasCeiling; ++ } ++ ++ public boolean isNether() { ++ return this.ultraWarm; ++ } ++ ++ public boolean isNatural() { ++ return this.natural; ++ } ++ ++ public double getCoordinateScale() { ++ return this.coordinateScale; ++ } ++ ++ public boolean isPiglinSafe() { ++ return this.piglinSafe; ++ } ++ ++ public boolean isBedWorks() { ++ return this.bedWorks; ++ } ++ ++ public boolean isRespawnAnchorWorks() { ++ return this.respawnAnchorWorks; ++ } ++ ++ public boolean hasRaids() { ++ return this.hasRaids; ++ } ++ ++ public int getLogicalHeight() { ++ return this.logicalHeight; ++ } ++ ++ public boolean isCreateDragonBattle() { ++ return this.createDragonBattle; ++ } ++ ++ public GenLayerZoomer getGenLayerZoomer() { ++ return this.genLayerZoomer; ++ } ++ ++ public boolean isFixedTime() { ++ return this.fixedTime.isPresent(); ++ } ++ ++ public float a(long i) { ++ double d0 = MathHelper.h((double) this.fixedTime.orElse(i) / 24000.0D - 0.25D); ++ double d1 = 0.5D - Math.cos(d0 * 3.141592653589793D) / 2.0D; ++ ++ return (float) (d0 * 2.0D + d1) / 3.0F; ++ } ++ ++ public int b(long i) { ++ return (int) (i / 24000L % 8L + 8L) % 8; ++ } ++ ++ public float a(int i) { ++ return this.E[i]; ++ } ++ ++ public Tag o() { ++ Tag tag = TagsBlock.a().a(this.infiniburn); ++ ++ return (Tag) (tag != null ? tag : TagsBlock.aE); ++ } ++ ++ public boolean a(DimensionManager dimensionmanager) { ++ return this == dimensionmanager ? true : this.hasSkylight == dimensionmanager.hasSkylight && this.hasCeiling == dimensionmanager.hasCeiling && this.ultraWarm == dimensionmanager.ultraWarm && this.natural == dimensionmanager.natural && this.coordinateScale == dimensionmanager.coordinateScale && this.createDragonBattle == dimensionmanager.createDragonBattle && this.piglinSafe == dimensionmanager.piglinSafe && this.bedWorks == dimensionmanager.bedWorks && this.respawnAnchorWorks == dimensionmanager.respawnAnchorWorks && this.hasRaids == dimensionmanager.hasRaids && this.logicalHeight == dimensionmanager.logicalHeight && Float.compare(dimensionmanager.ambientLight, this.ambientLight) == 0 && this.fixedTime.equals(dimensionmanager.fixedTime) && this.genLayerZoomer.equals(dimensionmanager.genLayerZoomer) && this.infiniburn.equals(dimensionmanager.infiniburn) && this.effects.equals(dimensionmanager.effects); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +new file mode 100644 +index 0000000000000000000000000000000000000000..38c20c4a8521560eac52e06f848ef835103f107f +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +@@ -0,0 +1,564 @@ ++package net.minecraft.world.level.dimension.end; ++ ++import com.google.common.collect.ContiguousSet; ++import com.google.common.collect.DiscreteDomain; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Range; ++import com.google.common.collect.Sets; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Random; ++import java.util.Set; ++import java.util.UUID; ++import java.util.function.Predicate; ++import javax.annotation.Nullable; ++import net.minecraft.advancements.CriterionTriggers; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.data.worldgen.BiomeDecoratorGroups; ++import net.minecraft.nbt.GameProfileSerializer; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagInt; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.network.chat.ChatMessage; ++import net.minecraft.server.level.BossBattleServer; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.PlayerChunk; ++import net.minecraft.server.level.TicketType; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.util.MathHelper; ++import net.minecraft.util.Unit; ++import net.minecraft.world.BossBattle; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.IEntitySelector; ++import net.minecraft.world.entity.boss.enderdragon.EntityEnderCrystal; ++import net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon; ++import net.minecraft.world.entity.boss.enderdragon.phases.DragonControllerPhase; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.GeneratorAccessSeed; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.entity.TileEntityEnderPortal; ++import net.minecraft.world.level.block.state.pattern.ShapeDetector; ++import net.minecraft.world.level.block.state.pattern.ShapeDetectorBlock; ++import net.minecraft.world.level.block.state.pattern.ShapeDetectorBuilder; ++import net.minecraft.world.level.block.state.predicate.BlockPredicate; ++import net.minecraft.world.level.chunk.Chunk; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.levelgen.HeightMap; ++import net.minecraft.world.level.levelgen.feature.WorldGenEndTrophy; ++import net.minecraft.world.level.levelgen.feature.WorldGenEnder; ++import net.minecraft.world.level.levelgen.feature.configurations.WorldGenFeatureConfiguration; ++import net.minecraft.world.phys.AxisAlignedBB; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class EnderDragonBattle { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private static final Predicate b = IEntitySelector.a.and(IEntitySelector.a(0.0D, 128.0D, 0.0D, 192.0D)); ++ public final BossBattleServer bossBattle; ++ public final WorldServer world; ++ private final List gateways; ++ private final ShapeDetector f; ++ private int g; ++ private int h; ++ private int i; ++ private int j; ++ private boolean dragonKilled; ++ private boolean previouslyKilled; ++ public UUID dragonUUID; ++ private boolean n; ++ public BlockPosition exitPortalLocation; ++ public EnumDragonRespawn respawnPhase; ++ private int q; ++ private List r; ++ ++ public EnderDragonBattle(WorldServer worldserver, long i, NBTTagCompound nbttagcompound) { ++ this.bossBattle = (BossBattleServer) (new BossBattleServer(new ChatMessage("entity.minecraft.ender_dragon"), BossBattle.BarColor.PINK, BossBattle.BarStyle.PROGRESS)).setPlayMusic(true).setCreateFog(true); ++ this.gateways = Lists.newArrayList(); ++ this.n = true; ++ this.world = worldserver; ++ if (nbttagcompound.hasKeyOfType("DragonKilled", 99)) { ++ if (nbttagcompound.b("Dragon")) { ++ this.dragonUUID = nbttagcompound.a("Dragon"); ++ } ++ ++ this.dragonKilled = nbttagcompound.getBoolean("DragonKilled"); ++ this.previouslyKilled = nbttagcompound.getBoolean("PreviouslyKilled"); ++ if (nbttagcompound.getBoolean("IsRespawning")) { ++ this.respawnPhase = EnumDragonRespawn.START; ++ } ++ ++ if (nbttagcompound.hasKeyOfType("ExitPortalLocation", 10)) { ++ this.exitPortalLocation = GameProfileSerializer.b(nbttagcompound.getCompound("ExitPortalLocation")); ++ } ++ } else { ++ this.dragonKilled = true; ++ this.previouslyKilled = true; ++ } ++ ++ if (nbttagcompound.hasKeyOfType("Gateways", 9)) { ++ NBTTagList nbttaglist = nbttagcompound.getList("Gateways", 3); ++ ++ for (int j = 0; j < nbttaglist.size(); ++j) { ++ this.gateways.add(nbttaglist.e(j)); ++ } ++ } else { ++ this.gateways.addAll(ContiguousSet.create(Range.closedOpen(0, 20), DiscreteDomain.integers())); ++ Collections.shuffle(this.gateways, new Random(i)); ++ } ++ ++ this.f = ShapeDetectorBuilder.a().a(" ", " ", " ", " # ", " ", " ", " ").a(" ", " ", " ", " # ", " ", " ", " ").a(" ", " ", " ", " # ", " ", " ", " ").a(" ### ", " # # ", "# #", "# # #", "# #", " # # ", " ### ").a(" ", " ### ", " ##### ", " ##### ", " ##### ", " ### ", " ").a('#', ShapeDetectorBlock.a(BlockPredicate.a(Blocks.BEDROCK))).b(); ++ } ++ ++ public NBTTagCompound a() { ++ NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ ++ if (this.dragonUUID != null) { ++ nbttagcompound.a("Dragon", this.dragonUUID); ++ } ++ ++ nbttagcompound.setBoolean("DragonKilled", this.dragonKilled); ++ nbttagcompound.setBoolean("PreviouslyKilled", this.previouslyKilled); ++ if (this.exitPortalLocation != null) { ++ nbttagcompound.set("ExitPortalLocation", GameProfileSerializer.a(this.exitPortalLocation)); ++ } ++ ++ NBTTagList nbttaglist = new NBTTagList(); ++ Iterator iterator = this.gateways.iterator(); ++ ++ while (iterator.hasNext()) { ++ int i = (Integer) iterator.next(); ++ ++ nbttaglist.add(NBTTagInt.a(i)); ++ } ++ ++ nbttagcompound.set("Gateways", nbttaglist); ++ return nbttagcompound; ++ } ++ ++ public void b() { ++ this.bossBattle.setVisible(!this.dragonKilled); ++ if (++this.j >= 20) { ++ this.l(); ++ this.j = 0; ++ } ++ ++ if (!this.bossBattle.getPlayers().isEmpty()) { ++ this.world.getChunkProvider().addTicket(TicketType.DRAGON, new ChunkCoordIntPair(0, 0), 9, Unit.INSTANCE); ++ boolean flag = this.k(); ++ ++ if (this.n && flag) { ++ this.g(); ++ this.n = false; ++ } ++ ++ if (this.respawnPhase != null) { ++ if (this.r == null && flag) { ++ this.respawnPhase = null; ++ this.initiateRespawn(); ++ } ++ ++ this.respawnPhase.a(this.world, this, this.r, this.q++, this.exitPortalLocation); ++ } ++ ++ if (!this.dragonKilled) { ++ if ((this.dragonUUID == null || ++this.g >= 1200) && flag) { ++ this.h(); ++ this.g = 0; ++ } ++ ++ if (++this.i >= 100 && flag) { ++ this.m(); ++ this.i = 0; ++ } ++ } ++ } else { ++ this.world.getChunkProvider().removeTicket(TicketType.DRAGON, new ChunkCoordIntPair(0, 0), 9, Unit.INSTANCE); ++ } ++ ++ } ++ ++ private void g() { ++ EnderDragonBattle.LOGGER.info("Scanning for legacy world dragon fight..."); ++ boolean flag = this.i(); ++ ++ if (flag) { ++ EnderDragonBattle.LOGGER.info("Found that the dragon has been killed in this world already."); ++ this.previouslyKilled = true; ++ } else { ++ EnderDragonBattle.LOGGER.info("Found that the dragon has not yet been killed in this world."); ++ this.previouslyKilled = false; ++ if (this.getExitPortalShape() == null) { ++ this.generateExitPortal(false); ++ } ++ } ++ ++ List list = this.world.g(); ++ ++ if (list.isEmpty()) { ++ this.dragonKilled = true; ++ } else { ++ EntityEnderDragon entityenderdragon = (EntityEnderDragon) list.get(0); ++ ++ this.dragonUUID = entityenderdragon.getUniqueID(); ++ EnderDragonBattle.LOGGER.info("Found that there's a dragon still alive ({})", entityenderdragon); ++ this.dragonKilled = false; ++ if (!flag) { ++ EnderDragonBattle.LOGGER.info("But we didn't have a portal, let's remove it."); ++ entityenderdragon.die(); ++ this.dragonUUID = null; ++ } ++ } ++ ++ if (!this.previouslyKilled && this.dragonKilled) { ++ this.dragonKilled = false; ++ } ++ ++ } ++ ++ private void h() { ++ List list = this.world.g(); ++ ++ if (list.isEmpty()) { ++ EnderDragonBattle.LOGGER.debug("Haven't seen the dragon, respawning it"); ++ this.o(); ++ } else { ++ EnderDragonBattle.LOGGER.debug("Haven't seen our dragon, but found another one to use."); ++ this.dragonUUID = ((EntityEnderDragon) list.get(0)).getUniqueID(); ++ } ++ ++ } ++ ++ public void setRespawnPhase(EnumDragonRespawn enumdragonrespawn) { ++ if (this.respawnPhase == null) { ++ throw new IllegalStateException("Dragon respawn isn't in progress, can't skip ahead in the animation."); ++ } else { ++ this.q = 0; ++ if (enumdragonrespawn == EnumDragonRespawn.END) { ++ this.respawnPhase = null; ++ this.dragonKilled = false; ++ EntityEnderDragon entityenderdragon = this.o(); ++ Iterator iterator = this.bossBattle.getPlayers().iterator(); ++ ++ while (iterator.hasNext()) { ++ EntityPlayer entityplayer = (EntityPlayer) iterator.next(); ++ ++ CriterionTriggers.n.a(entityplayer, (Entity) entityenderdragon); ++ } ++ } else { ++ this.respawnPhase = enumdragonrespawn; ++ } ++ ++ } ++ } ++ ++ private boolean i() { ++ for (int i = -8; i <= 8; ++i) { ++ int j = -8; ++ ++ label27: ++ while (j <= 8) { ++ Chunk chunk = this.world.getChunkAt(i, j); ++ Iterator iterator = chunk.getTileEntities().values().iterator(); ++ ++ TileEntity tileentity; ++ ++ do { ++ if (!iterator.hasNext()) { ++ ++j; ++ continue label27; ++ } ++ ++ tileentity = (TileEntity) iterator.next(); ++ } while (!(tileentity instanceof TileEntityEnderPortal)); ++ ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ @Nullable ++ public ShapeDetector.ShapeDetectorCollection getExitPortalShape() { ++ int i; ++ int j; ++ ++ for (i = -8; i <= 8; ++i) { ++ for (j = -8; j <= 8; ++j) { ++ Chunk chunk = this.world.getChunkAt(i, j); ++ Iterator iterator = chunk.getTileEntities().values().iterator(); ++ ++ while (iterator.hasNext()) { ++ TileEntity tileentity = (TileEntity) iterator.next(); ++ ++ if (tileentity instanceof TileEntityEnderPortal) { ++ ShapeDetector.ShapeDetectorCollection shapedetector_shapedetectorcollection = this.f.a(this.world, tileentity.getPosition()); ++ ++ if (shapedetector_shapedetectorcollection != null) { ++ BlockPosition blockposition = shapedetector_shapedetectorcollection.a(3, 3, 3).getPosition(); ++ ++ if (this.exitPortalLocation == null && blockposition.getX() == 0 && blockposition.getZ() == 0) { ++ this.exitPortalLocation = blockposition; ++ } ++ ++ return shapedetector_shapedetectorcollection; ++ } ++ } ++ } ++ } ++ } ++ ++ i = this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, WorldGenEndTrophy.a).getY(); ++ ++ for (j = i; j >= 0; --j) { ++ ShapeDetector.ShapeDetectorCollection shapedetector_shapedetectorcollection1 = this.f.a(this.world, new BlockPosition(WorldGenEndTrophy.a.getX(), j, WorldGenEndTrophy.a.getZ())); ++ ++ if (shapedetector_shapedetectorcollection1 != null) { ++ if (this.exitPortalLocation == null) { ++ this.exitPortalLocation = shapedetector_shapedetectorcollection1.a(3, 3, 3).getPosition(); ++ } ++ ++ return shapedetector_shapedetectorcollection1; ++ } ++ } ++ ++ return null; ++ } ++ ++ private boolean k() { ++ for (int i = -8; i <= 8; ++i) { ++ for (int j = 8; j <= 8; ++j) { ++ IChunkAccess ichunkaccess = this.world.getChunkAt(i, j, ChunkStatus.FULL, false); ++ ++ if (!(ichunkaccess instanceof Chunk)) { ++ return false; ++ } ++ ++ PlayerChunk.State playerchunk_state = ((Chunk) ichunkaccess).getState(); ++ ++ if (!playerchunk_state.isAtLeast(PlayerChunk.State.TICKING)) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ private void l() { ++ Set set = Sets.newHashSet(); ++ Iterator iterator = this.world.a(EnderDragonBattle.b).iterator(); ++ ++ while (iterator.hasNext()) { ++ EntityPlayer entityplayer = (EntityPlayer) iterator.next(); ++ ++ this.bossBattle.addPlayer(entityplayer); ++ set.add(entityplayer); ++ } ++ ++ Set set1 = Sets.newHashSet(this.bossBattle.getPlayers()); ++ ++ set1.removeAll(set); ++ Iterator iterator1 = set1.iterator(); ++ ++ while (iterator1.hasNext()) { ++ EntityPlayer entityplayer1 = (EntityPlayer) iterator1.next(); ++ ++ this.bossBattle.removePlayer(entityplayer1); ++ } ++ ++ } ++ ++ private void m() { ++ this.i = 0; ++ this.h = 0; ++ ++ WorldGenEnder.Spike worldgenender_spike; ++ ++ for (Iterator iterator = WorldGenEnder.a((GeneratorAccessSeed) this.world).iterator(); iterator.hasNext(); this.h += this.world.a(EntityEnderCrystal.class, worldgenender_spike.f()).size()) { ++ worldgenender_spike = (WorldGenEnder.Spike) iterator.next(); ++ } ++ ++ EnderDragonBattle.LOGGER.debug("Found {} end crystals still alive", this.h); ++ } ++ ++ public void a(EntityEnderDragon entityenderdragon) { ++ if (entityenderdragon.getUniqueID().equals(this.dragonUUID)) { ++ this.bossBattle.setProgress(0.0F); ++ this.bossBattle.setVisible(false); ++ this.generateExitPortal(true); ++ this.n(); ++ if (!this.previouslyKilled) { ++ this.world.setTypeUpdate(this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, WorldGenEndTrophy.a), Blocks.DRAGON_EGG.getBlockData()); ++ } ++ ++ this.previouslyKilled = true; ++ this.dragonKilled = true; ++ } ++ ++ } ++ ++ private void n() { ++ if (!this.gateways.isEmpty()) { ++ int i = (Integer) this.gateways.remove(this.gateways.size() - 1); ++ int j = MathHelper.floor(96.0D * Math.cos(2.0D * (-3.141592653589793D + 0.15707963267948966D * (double) i))); ++ int k = MathHelper.floor(96.0D * Math.sin(2.0D * (-3.141592653589793D + 0.15707963267948966D * (double) i))); ++ ++ this.a(new BlockPosition(j, 75, k)); ++ } ++ } ++ ++ private void a(BlockPosition blockposition) { ++ this.world.triggerEffect(3000, blockposition, 0); ++ BiomeDecoratorGroups.END_GATEWAY_DELAYED.a(this.world, this.world.getChunkProvider().getChunkGenerator(), new Random(), blockposition); ++ } ++ ++ public void generateExitPortal(boolean flag) { ++ WorldGenEndTrophy worldgenendtrophy = new WorldGenEndTrophy(flag); ++ ++ if (this.exitPortalLocation == null) { ++ for (this.exitPortalLocation = this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING_NO_LEAVES, WorldGenEndTrophy.a).down(); this.world.getType(this.exitPortalLocation).a(Blocks.BEDROCK) && this.exitPortalLocation.getY() > this.world.getSeaLevel(); this.exitPortalLocation = this.exitPortalLocation.down()) { ++ ; ++ } ++ } ++ ++ worldgenendtrophy.b((WorldGenFeatureConfiguration) WorldGenFeatureConfiguration.k).a(this.world, this.world.getChunkProvider().getChunkGenerator(), new Random(), this.exitPortalLocation); ++ } ++ ++ private EntityEnderDragon o() { ++ this.world.getChunkAtWorldCoords(new BlockPosition(0, 128, 0)); ++ EntityEnderDragon entityenderdragon = (EntityEnderDragon) EntityTypes.ENDER_DRAGON.a((World) this.world); ++ ++ entityenderdragon.getDragonControllerManager().setControllerPhase(DragonControllerPhase.HOLDING_PATTERN); ++ entityenderdragon.setPositionRotation(0.0D, 128.0D, 0.0D, this.world.random.nextFloat() * 360.0F, 0.0F); ++ this.world.addEntity(entityenderdragon); ++ this.dragonUUID = entityenderdragon.getUniqueID(); ++ return entityenderdragon; ++ } ++ ++ public void b(EntityEnderDragon entityenderdragon) { ++ if (entityenderdragon.getUniqueID().equals(this.dragonUUID)) { ++ this.bossBattle.setProgress(entityenderdragon.getHealth() / entityenderdragon.getMaxHealth()); ++ this.g = 0; ++ if (entityenderdragon.hasCustomName()) { ++ this.bossBattle.a(entityenderdragon.getScoreboardDisplayName()); ++ } ++ } ++ ++ } ++ ++ public int c() { ++ return this.h; ++ } ++ ++ public void a(EntityEnderCrystal entityendercrystal, DamageSource damagesource) { ++ if (this.respawnPhase != null && this.r.contains(entityendercrystal)) { ++ EnderDragonBattle.LOGGER.debug("Aborting respawn sequence"); ++ this.respawnPhase = null; ++ this.q = 0; ++ this.resetCrystals(); ++ this.generateExitPortal(true); ++ } else { ++ this.m(); ++ Entity entity = this.world.getEntity(this.dragonUUID); ++ ++ if (entity instanceof EntityEnderDragon) { ++ ((EntityEnderDragon) entity).a(entityendercrystal, entityendercrystal.getChunkCoordinates(), damagesource); ++ } ++ } ++ ++ } ++ ++ public boolean isPreviouslyKilled() { ++ return this.previouslyKilled; ++ } ++ ++ public void initiateRespawn() { ++ if (this.dragonKilled && this.respawnPhase == null) { ++ BlockPosition blockposition = this.exitPortalLocation; ++ ++ if (blockposition == null) { ++ EnderDragonBattle.LOGGER.debug("Tried to respawn, but need to find the portal first."); ++ ShapeDetector.ShapeDetectorCollection shapedetector_shapedetectorcollection = this.getExitPortalShape(); ++ ++ if (shapedetector_shapedetectorcollection == null) { ++ EnderDragonBattle.LOGGER.debug("Couldn't find a portal, so we made one."); ++ this.generateExitPortal(true); ++ } else { ++ EnderDragonBattle.LOGGER.debug("Found the exit portal & temporarily using it."); ++ } ++ ++ blockposition = this.exitPortalLocation; ++ } ++ ++ List list = Lists.newArrayList(); ++ BlockPosition blockposition1 = blockposition.up(1); ++ Iterator iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator(); ++ ++ while (iterator.hasNext()) { ++ EnumDirection enumdirection = (EnumDirection) iterator.next(); ++ List list1 = this.world.a(EntityEnderCrystal.class, new AxisAlignedBB(blockposition1.shift(enumdirection, 2))); ++ ++ if (list1.isEmpty()) { ++ return; ++ } ++ ++ list.addAll(list1); ++ } ++ ++ EnderDragonBattle.LOGGER.debug("Found all crystals, respawning dragon."); ++ this.a((List) list); ++ } ++ ++ } ++ ++ private void a(List list) { ++ if (this.dragonKilled && this.respawnPhase == null) { ++ for (ShapeDetector.ShapeDetectorCollection shapedetector_shapedetectorcollection = this.getExitPortalShape(); shapedetector_shapedetectorcollection != null; shapedetector_shapedetectorcollection = this.getExitPortalShape()) { ++ for (int i = 0; i < this.f.c(); ++i) { ++ for (int j = 0; j < this.f.b(); ++j) { ++ for (int k = 0; k < this.f.a(); ++k) { ++ ShapeDetectorBlock shapedetectorblock = shapedetector_shapedetectorcollection.a(i, j, k); ++ ++ if (shapedetectorblock.a().a(Blocks.BEDROCK) || shapedetectorblock.a().a(Blocks.END_PORTAL)) { ++ this.world.setTypeUpdate(shapedetectorblock.getPosition(), Blocks.END_STONE.getBlockData()); ++ } ++ } ++ } ++ } ++ } ++ ++ this.respawnPhase = EnumDragonRespawn.START; ++ this.q = 0; ++ this.generateExitPortal(false); ++ this.r = list; ++ } ++ ++ } ++ ++ public void resetCrystals() { ++ Iterator iterator = WorldGenEnder.a((GeneratorAccessSeed) this.world).iterator(); ++ ++ while (iterator.hasNext()) { ++ WorldGenEnder.Spike worldgenender_spike = (WorldGenEnder.Spike) iterator.next(); ++ List list = this.world.a(EntityEnderCrystal.class, worldgenender_spike.f()); ++ Iterator iterator1 = list.iterator(); ++ ++ while (iterator1.hasNext()) { ++ EntityEnderCrystal entityendercrystal = (EntityEnderCrystal) iterator1.next(); ++ ++ entityendercrystal.setInvulnerable(false); ++ entityendercrystal.setBeamTarget((BlockPosition) null); ++ } ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/levelgen/HeightMap.java b/src/main/java/net/minecraft/world/level/levelgen/HeightMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bcb620e9b7f47341f51af0f3bb7fbd6a348f9739 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/levelgen/HeightMap.java +@@ -0,0 +1,195 @@ ++package net.minecraft.world.level.levelgen; ++ ++import com.google.common.collect.Maps; ++import com.mojang.serialization.Codec; ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; ++import it.unimi.dsi.fastutil.objects.ObjectList; ++import it.unimi.dsi.fastutil.objects.ObjectListIterator; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Set; ++import java.util.function.Predicate; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.util.DataBits; ++import net.minecraft.util.INamable; ++import net.minecraft.world.level.block.BlockLeaves; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.IChunkAccess; ++ ++public class HeightMap { ++ ++ private static final Predicate a = (iblockdata) -> { ++ return !iblockdata.isAir(); ++ }; ++ private static final Predicate b = (iblockdata) -> { ++ return iblockdata.getMaterial().isSolid(); ++ }; ++ private final DataBits c = new DataBits(9, 256); ++ private final Predicate d; ++ private final IChunkAccess e; ++ ++ public HeightMap(IChunkAccess ichunkaccess, HeightMap.Type heightmap_type) { ++ this.d = heightmap_type.e(); ++ this.e = ichunkaccess; ++ } ++ ++ public static void a(IChunkAccess ichunkaccess, Set set) { ++ int i = set.size(); ++ ObjectList objectlist = new ObjectArrayList(i); ++ ObjectListIterator objectlistiterator = objectlist.iterator(); ++ int j = ichunkaccess.b() + 16; ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ ++ for (int k = 0; k < 16; ++k) { ++ for (int l = 0; l < 16; ++l) { ++ Iterator iterator = set.iterator(); ++ ++ while (iterator.hasNext()) { ++ HeightMap.Type heightmap_type = (HeightMap.Type) iterator.next(); ++ ++ objectlist.add(ichunkaccess.a(heightmap_type)); ++ } ++ ++ for (int i1 = j - 1; i1 >= 0; --i1) { ++ blockposition_mutableblockposition.d(k, i1, l); ++ IBlockData iblockdata = ichunkaccess.getType(blockposition_mutableblockposition); ++ ++ if (!iblockdata.a(Blocks.AIR)) { ++ while (objectlistiterator.hasNext()) { ++ HeightMap heightmap = (HeightMap) objectlistiterator.next(); ++ ++ if (heightmap.d.test(iblockdata)) { ++ heightmap.a(k, l, i1 + 1); ++ objectlistiterator.remove(); ++ } ++ } ++ ++ if (objectlist.isEmpty()) { ++ break; ++ } ++ ++ objectlistiterator.back(i); ++ } ++ } ++ } ++ } ++ ++ } ++ ++ public boolean a(int i, int j, int k, IBlockData iblockdata) { ++ int l = this.a(i, k); ++ ++ if (j <= l - 2) { ++ return false; ++ } else { ++ if (this.d.test(iblockdata)) { ++ if (j >= l) { ++ this.a(i, k, j + 1); ++ return true; ++ } ++ } else if (l - 1 == j) { ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ ++ for (int i1 = j - 1; i1 >= 0; --i1) { ++ blockposition_mutableblockposition.d(i, i1, k); ++ if (this.d.test(this.e.getType(blockposition_mutableblockposition))) { ++ this.a(i, k, i1 + 1); ++ return true; ++ } ++ } ++ ++ this.a(i, k, 0); ++ return true; ++ } ++ ++ return false; ++ } ++ } ++ ++ public int a(int i, int j) { ++ return this.a(c(i, j)); ++ } ++ ++ private int a(int i) { ++ return this.c.a(i); ++ } ++ ++ private void a(int i, int j, int k) { ++ this.c.b(c(i, j), k); ++ } ++ ++ public void a(long[] along) { ++ System.arraycopy(along, 0, this.c.a(), 0, along.length); ++ } ++ ++ public long[] a() { ++ return this.c.a(); ++ } ++ ++ private static int c(int i, int j) { ++ return i + j * 16; ++ } ++ ++ public static enum Type implements INamable { ++ ++ WORLD_SURFACE_WG("WORLD_SURFACE_WG", HeightMap.Use.WORLDGEN, HeightMap.a), WORLD_SURFACE("WORLD_SURFACE", HeightMap.Use.CLIENT, HeightMap.a), OCEAN_FLOOR_WG("OCEAN_FLOOR_WG", HeightMap.Use.WORLDGEN, HeightMap.b), OCEAN_FLOOR("OCEAN_FLOOR", HeightMap.Use.LIVE_WORLD, HeightMap.b), MOTION_BLOCKING("MOTION_BLOCKING", HeightMap.Use.CLIENT, (iblockdata) -> { ++ return iblockdata.getMaterial().isSolid() || !iblockdata.getFluid().isEmpty(); ++ }), MOTION_BLOCKING_NO_LEAVES("MOTION_BLOCKING_NO_LEAVES", HeightMap.Use.LIVE_WORLD, (iblockdata) -> { ++ return (iblockdata.getMaterial().isSolid() || !iblockdata.getFluid().isEmpty()) && !(iblockdata.getBlock() instanceof BlockLeaves); ++ }); ++ ++ public static final Codec g = INamable.a(HeightMap.Type::values, HeightMap.Type::a); ++ private final String h; ++ private final HeightMap.Use i; ++ private final Predicate j; ++ private static final Map k = (Map) SystemUtils.a((Object) Maps.newHashMap(), (hashmap) -> { ++ HeightMap.Type[] aheightmap_type = values(); ++ int i = aheightmap_type.length; ++ ++ for (int j = 0; j < i; ++j) { ++ HeightMap.Type heightmap_type = aheightmap_type[j]; ++ ++ hashmap.put(heightmap_type.h, heightmap_type); ++ } ++ ++ }); ++ ++ private Type(String s, HeightMap.Use heightmap_use, Predicate predicate) { ++ this.h = s; ++ this.i = heightmap_use; ++ this.j = predicate; ++ } ++ ++ public String b() { ++ return this.h; ++ } ++ ++ public boolean c() { ++ return this.i == HeightMap.Use.CLIENT; ++ } ++ ++ @Nullable ++ public static HeightMap.Type a(String s) { ++ return (HeightMap.Type) HeightMap.Type.k.get(s); ++ } ++ ++ public Predicate e() { ++ return this.j; ++ } ++ ++ @Override ++ public String getName() { ++ return this.h; ++ } ++ } ++ ++ public static enum Use { ++ ++ WORLDGEN, LIVE_WORLD, CLIENT; ++ ++ private Use() {} ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..44ad3fb2551f681b58b82e7c4f56bbc5a3b4486e +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java +@@ -0,0 +1,278 @@ ++package net.minecraft.world.level.levelgen.feature; ++ ++import com.google.common.collect.BiMap; ++import com.google.common.collect.HashBiMap; ++import com.google.common.collect.ImmutableList; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Maps; ++import com.mojang.serialization.Codec; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.IRegistry; ++import net.minecraft.core.IRegistryCustom; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.IWorldReader; ++import net.minecraft.world.level.StructureManager; ++import net.minecraft.world.level.biome.BiomeBase; ++import net.minecraft.world.level.biome.BiomeSettingsMobs; ++import net.minecraft.world.level.biome.WorldChunkManager; ++import net.minecraft.world.level.chunk.ChunkGenerator; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.levelgen.SeededRandom; ++import net.minecraft.world.level.levelgen.WorldGenStage; ++import net.minecraft.world.level.levelgen.feature.configurations.StructureSettingsFeature; ++import net.minecraft.world.level.levelgen.feature.configurations.WorldGenFeatureConfiguration; ++import net.minecraft.world.level.levelgen.feature.configurations.WorldGenFeatureConfigurationChance; ++import net.minecraft.world.level.levelgen.feature.configurations.WorldGenFeatureEmptyConfiguration; ++import net.minecraft.world.level.levelgen.feature.configurations.WorldGenFeatureOceanRuinConfiguration; ++import net.minecraft.world.level.levelgen.feature.configurations.WorldGenFeatureRuinedPortalConfiguration; ++import net.minecraft.world.level.levelgen.feature.configurations.WorldGenFeatureShipwreckConfiguration; ++import net.minecraft.world.level.levelgen.feature.configurations.WorldGenFeatureVillageConfiguration; ++import net.minecraft.world.level.levelgen.feature.configurations.WorldGenMineshaftConfiguration; ++import net.minecraft.world.level.levelgen.structure.StructureBoundingBox; ++import net.minecraft.world.level.levelgen.structure.StructurePiece; ++import net.minecraft.world.level.levelgen.structure.StructureStart; ++import net.minecraft.world.level.levelgen.structure.WorldGenFeatureNetherFossil; ++import net.minecraft.world.level.levelgen.structure.WorldGenFeatureOceanRuin; ++import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructureManager; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public abstract class StructureGenerator { ++ ++ public static final BiMap> a = HashBiMap.create(); ++ private static final Map, WorldGenStage.Decoration> u = Maps.newHashMap(); ++ private static final Logger LOGGER = LogManager.getLogger(); ++ public static final StructureGenerator PILLAGER_OUTPOST = a("Pillager_Outpost", new WorldGenFeaturePillagerOutpost(WorldGenFeatureVillageConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final StructureGenerator MINESHAFT = a("Mineshaft", new WorldGenMineshaft(WorldGenMineshaftConfiguration.a), WorldGenStage.Decoration.UNDERGROUND_STRUCTURES); ++ public static final StructureGenerator MANSION = a("Mansion", new WorldGenWoodlandMansion(WorldGenFeatureEmptyConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final StructureGenerator JUNGLE_PYRAMID = a("Jungle_Pyramid", new WorldGenFeatureJunglePyramid(WorldGenFeatureEmptyConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final StructureGenerator DESERT_PYRAMID = a("Desert_Pyramid", new WorldGenFeatureDesertPyramid(WorldGenFeatureEmptyConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final StructureGenerator IGLOO = a("Igloo", new WorldGenFeatureIgloo(WorldGenFeatureEmptyConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final StructureGenerator RUINED_PORTAL = a("Ruined_Portal", new WorldGenFeatureRuinedPortal(WorldGenFeatureRuinedPortalConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final StructureGenerator SHIPWRECK = a("Shipwreck", new WorldGenFeatureShipwreck(WorldGenFeatureShipwreckConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final WorldGenFeatureSwampHut SWAMP_HUT = (WorldGenFeatureSwampHut) a("Swamp_Hut", new WorldGenFeatureSwampHut(WorldGenFeatureEmptyConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final StructureGenerator STRONGHOLD = a("Stronghold", new WorldGenStronghold(WorldGenFeatureEmptyConfiguration.a), WorldGenStage.Decoration.STRONGHOLDS); ++ public static final StructureGenerator MONUMENT = a("Monument", new WorldGenMonument(WorldGenFeatureEmptyConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final StructureGenerator OCEAN_RUIN = a("Ocean_Ruin", new WorldGenFeatureOceanRuin(WorldGenFeatureOceanRuinConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final StructureGenerator FORTRESS = a("Fortress", new WorldGenNether(WorldGenFeatureEmptyConfiguration.a), WorldGenStage.Decoration.UNDERGROUND_DECORATION); ++ public static final StructureGenerator ENDCITY = a("EndCity", new WorldGenEndCity(WorldGenFeatureEmptyConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final StructureGenerator BURIED_TREASURE = a("Buried_Treasure", new WorldGenBuriedTreasure(WorldGenFeatureConfigurationChance.b), WorldGenStage.Decoration.UNDERGROUND_STRUCTURES); ++ public static final StructureGenerator VILLAGE = a("Village", new WorldGenVillage(WorldGenFeatureVillageConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final StructureGenerator NETHER_FOSSIL = a("Nether_Fossil", new WorldGenFeatureNetherFossil(WorldGenFeatureEmptyConfiguration.a), WorldGenStage.Decoration.UNDERGROUND_DECORATION); ++ public static final StructureGenerator BASTION_REMNANT = a("Bastion_Remnant", new WorldGenFeatureBastionRemnant(WorldGenFeatureVillageConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); ++ public static final List> t = ImmutableList.of(StructureGenerator.PILLAGER_OUTPOST, StructureGenerator.VILLAGE, StructureGenerator.NETHER_FOSSIL); ++ private static final MinecraftKey w = new MinecraftKey("jigsaw"); ++ private static final Map x = ImmutableMap.builder().put(new MinecraftKey("nvi"), StructureGenerator.w).put(new MinecraftKey("pcp"), StructureGenerator.w).put(new MinecraftKey("bastionremnant"), StructureGenerator.w).put(new MinecraftKey("runtime"), StructureGenerator.w).build(); ++ private final Codec>> y; ++ ++ private static > F a(String s, F f0, WorldGenStage.Decoration worldgenstage_decoration) { ++ StructureGenerator.a.put(s.toLowerCase(Locale.ROOT), f0); ++ StructureGenerator.u.put(f0, worldgenstage_decoration); ++ return (StructureGenerator) IRegistry.a(IRegistry.STRUCTURE_FEATURE, s.toLowerCase(Locale.ROOT), (Object) f0); ++ } ++ ++ public StructureGenerator(Codec codec) { ++ this.y = codec.fieldOf("config").xmap((worldgenfeatureconfiguration) -> { ++ return new StructureFeature<>(this, worldgenfeatureconfiguration); ++ }, (structurefeature) -> { ++ return structurefeature.e; ++ }).codec(); ++ } ++ ++ public WorldGenStage.Decoration f() { ++ return (WorldGenStage.Decoration) StructureGenerator.u.get(this); ++ } ++ ++ public static void g() {} ++ ++ @Nullable ++ public static StructureStart a(DefinedStructureManager definedstructuremanager, NBTTagCompound nbttagcompound, long i) { ++ String s = nbttagcompound.getString("id"); ++ ++ if ("INVALID".equals(s)) { ++ return StructureStart.a; ++ } else { ++ StructureGenerator structuregenerator = (StructureGenerator) IRegistry.STRUCTURE_FEATURE.get(new MinecraftKey(s.toLowerCase(Locale.ROOT))); ++ ++ if (structuregenerator == null) { ++ StructureGenerator.LOGGER.error("Unknown feature id: {}", s); ++ return null; ++ } else { ++ int j = nbttagcompound.getInt("ChunkX"); ++ int k = nbttagcompound.getInt("ChunkZ"); ++ int l = nbttagcompound.getInt("references"); ++ StructureBoundingBox structureboundingbox = nbttagcompound.hasKey("BB") ? new StructureBoundingBox(nbttagcompound.getIntArray("BB")) : StructureBoundingBox.a(); ++ NBTTagList nbttaglist = nbttagcompound.getList("Children", 10); ++ ++ try { ++ StructureStart structurestart = structuregenerator.a(j, k, structureboundingbox, l, i); ++ ++ for (int i1 = 0; i1 < nbttaglist.size(); ++i1) { ++ NBTTagCompound nbttagcompound1 = nbttaglist.getCompound(i1); ++ String s1 = nbttagcompound1.getString("id").toLowerCase(Locale.ROOT); ++ MinecraftKey minecraftkey = new MinecraftKey(s1); ++ MinecraftKey minecraftkey1 = (MinecraftKey) StructureGenerator.x.getOrDefault(minecraftkey, minecraftkey); ++ WorldGenFeatureStructurePieceType worldgenfeaturestructurepiecetype = (WorldGenFeatureStructurePieceType) IRegistry.STRUCTURE_PIECE.get(minecraftkey1); ++ ++ if (worldgenfeaturestructurepiecetype == null) { ++ StructureGenerator.LOGGER.error("Unknown structure piece id: {}", minecraftkey1); ++ } else { ++ try { ++ StructurePiece structurepiece = worldgenfeaturestructurepiecetype.load(definedstructuremanager, nbttagcompound1); ++ ++ structurestart.d().add(structurepiece); ++ } catch (Exception exception) { ++ StructureGenerator.LOGGER.error("Exception loading structure piece with id {}", minecraftkey1, exception); ++ } ++ } ++ } ++ ++ return structurestart; ++ } catch (Exception exception1) { ++ StructureGenerator.LOGGER.error("Failed Start with id {}", s, exception1); ++ return null; ++ } ++ } ++ } ++ } ++ ++ public Codec>> h() { ++ return this.y; ++ } ++ ++ public StructureFeature> a(C c0) { ++ return new StructureFeature<>(this, c0); ++ } ++ ++ @Nullable ++ public BlockPosition getNearestGeneratedFeature(IWorldReader iworldreader, StructureManager structuremanager, BlockPosition blockposition, int i, boolean flag, long j, StructureSettingsFeature structuresettingsfeature) { ++ int k = structuresettingsfeature.a(); ++ int l = blockposition.getX() >> 4; ++ int i1 = blockposition.getZ() >> 4; ++ int j1 = 0; ++ SeededRandom seededrandom = new SeededRandom(); ++ ++ while (j1 <= i) { ++ int k1 = -j1; ++ ++ while (true) { ++ if (k1 <= j1) { ++ boolean flag1 = k1 == -j1 || k1 == j1; ++ ++ for (int l1 = -j1; l1 <= j1; ++l1) { ++ boolean flag2 = l1 == -j1 || l1 == j1; ++ ++ if (flag1 || flag2) { ++ int i2 = l + k * k1; ++ int j2 = i1 + k * l1; ++ ChunkCoordIntPair chunkcoordintpair = this.a(structuresettingsfeature, j, seededrandom, i2, j2); ++ IChunkAccess ichunkaccess = iworldreader.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.STRUCTURE_STARTS); ++ StructureStart structurestart = structuremanager.a(SectionPosition.a(ichunkaccess.getPos(), 0), this, ichunkaccess); ++ ++ if (structurestart != null && structurestart.e()) { ++ if (flag && structurestart.h()) { ++ structurestart.i(); ++ return structurestart.a(); ++ } ++ ++ if (!flag) { ++ return structurestart.a(); ++ } ++ } ++ ++ if (j1 == 0) { ++ break; ++ } ++ } ++ } ++ ++ if (j1 != 0) { ++ ++k1; ++ continue; ++ } ++ } ++ ++ ++j1; ++ break; ++ } ++ } ++ ++ return null; ++ } ++ ++ protected boolean b() { ++ return true; ++ } ++ ++ public final ChunkCoordIntPair a(StructureSettingsFeature structuresettingsfeature, long i, SeededRandom seededrandom, int j, int k) { ++ int l = structuresettingsfeature.a(); ++ int i1 = structuresettingsfeature.b(); ++ int j1 = Math.floorDiv(j, l); ++ int k1 = Math.floorDiv(k, l); ++ ++ seededrandom.a(i, j1, k1, structuresettingsfeature.c()); ++ int l1; ++ int i2; ++ ++ if (this.b()) { ++ l1 = seededrandom.nextInt(l - i1); ++ i2 = seededrandom.nextInt(l - i1); ++ } else { ++ l1 = (seededrandom.nextInt(l - i1) + seededrandom.nextInt(l - i1)) / 2; ++ i2 = (seededrandom.nextInt(l - i1) + seededrandom.nextInt(l - i1)) / 2; ++ } ++ ++ return new ChunkCoordIntPair(j1 * l + l1, k1 * l + i2); ++ } ++ ++ protected boolean a(ChunkGenerator chunkgenerator, WorldChunkManager worldchunkmanager, long i, SeededRandom seededrandom, int j, int k, BiomeBase biomebase, ChunkCoordIntPair chunkcoordintpair, C c0) { ++ return true; ++ } ++ ++ private StructureStart a(int i, int j, StructureBoundingBox structureboundingbox, int k, long l) { ++ return this.a().create(this, i, j, structureboundingbox, k, l); ++ } ++ ++ public StructureStart a(IRegistryCustom iregistrycustom, ChunkGenerator chunkgenerator, WorldChunkManager worldchunkmanager, DefinedStructureManager definedstructuremanager, long i, ChunkCoordIntPair chunkcoordintpair, BiomeBase biomebase, int j, SeededRandom seededrandom, StructureSettingsFeature structuresettingsfeature, C c0) { ++ ChunkCoordIntPair chunkcoordintpair1 = this.a(structuresettingsfeature, i, seededrandom, chunkcoordintpair.x, chunkcoordintpair.z); ++ ++ if (chunkcoordintpair.x == chunkcoordintpair1.x && chunkcoordintpair.z == chunkcoordintpair1.z && this.a(chunkgenerator, worldchunkmanager, i, seededrandom, chunkcoordintpair.x, chunkcoordintpair.z, biomebase, chunkcoordintpair1, c0)) { ++ StructureStart structurestart = this.a(chunkcoordintpair.x, chunkcoordintpair.z, StructureBoundingBox.a(), j, i); ++ ++ structurestart.a(iregistrycustom, chunkgenerator, definedstructuremanager, chunkcoordintpair.x, chunkcoordintpair.z, biomebase, c0); ++ if (structurestart.e()) { ++ return structurestart; ++ } ++ } ++ ++ return StructureStart.a; ++ } ++ ++ public abstract StructureGenerator.a a(); ++ ++ public String i() { ++ return (String) StructureGenerator.a.inverse().get(this); ++ } ++ ++ public List c() { ++ return ImmutableList.of(); ++ } ++ ++ public List j() { ++ return ImmutableList.of(); ++ } ++ ++ public interface a { ++ ++ StructureStart create(StructureGenerator structuregenerator, int i, int j, StructureBoundingBox structureboundingbox, int k, long l); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/WorldGenEndTrophy.java b/src/main/java/net/minecraft/world/level/levelgen/feature/WorldGenEndTrophy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..18395a3b4a7df1c99e952b9c8e738f165648eba5 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/WorldGenEndTrophy.java +@@ -0,0 +1,66 @@ ++package net.minecraft.world.level.levelgen.feature; ++ ++import java.util.Iterator; ++import java.util.Random; ++import net.minecraft.core.BaseBlockPosition; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.world.level.GeneratorAccessSeed; ++import net.minecraft.world.level.block.BlockTorchWall; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.ChunkGenerator; ++import net.minecraft.world.level.levelgen.feature.configurations.WorldGenFeatureEmptyConfiguration; ++ ++public class WorldGenEndTrophy extends WorldGenerator { ++ ++ public static final BlockPosition a = BlockPosition.ZERO; ++ private final boolean ab; ++ ++ public WorldGenEndTrophy(boolean flag) { ++ super(WorldGenFeatureEmptyConfiguration.a); ++ this.ab = flag; ++ } ++ ++ public boolean a(GeneratorAccessSeed generatoraccessseed, ChunkGenerator chunkgenerator, Random random, BlockPosition blockposition, WorldGenFeatureEmptyConfiguration worldgenfeatureemptyconfiguration) { ++ Iterator iterator = BlockPosition.a(new BlockPosition(blockposition.getX() - 4, blockposition.getY() - 1, blockposition.getZ() - 4), new BlockPosition(blockposition.getX() + 4, blockposition.getY() + 32, blockposition.getZ() + 4)).iterator(); ++ ++ while (iterator.hasNext()) { ++ BlockPosition blockposition1 = (BlockPosition) iterator.next(); ++ boolean flag = blockposition1.a((BaseBlockPosition) blockposition, 2.5D); ++ ++ if (flag || blockposition1.a((BaseBlockPosition) blockposition, 3.5D)) { ++ if (blockposition1.getY() < blockposition.getY()) { ++ if (flag) { ++ this.a(generatoraccessseed, blockposition1, Blocks.BEDROCK.getBlockData()); ++ } else if (blockposition1.getY() < blockposition.getY()) { ++ this.a(generatoraccessseed, blockposition1, Blocks.END_STONE.getBlockData()); ++ } ++ } else if (blockposition1.getY() > blockposition.getY()) { ++ this.a(generatoraccessseed, blockposition1, Blocks.AIR.getBlockData()); ++ } else if (!flag) { ++ this.a(generatoraccessseed, blockposition1, Blocks.BEDROCK.getBlockData()); ++ } else if (this.ab) { ++ this.a(generatoraccessseed, new BlockPosition(blockposition1), Blocks.END_PORTAL.getBlockData()); ++ } else { ++ this.a(generatoraccessseed, new BlockPosition(blockposition1), Blocks.AIR.getBlockData()); ++ } ++ } ++ } ++ ++ for (int i = 0; i < 4; ++i) { ++ this.a(generatoraccessseed, blockposition.up(i), Blocks.BEDROCK.getBlockData()); ++ } ++ ++ BlockPosition blockposition2 = blockposition.up(2); ++ Iterator iterator1 = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator(); ++ ++ while (iterator1.hasNext()) { ++ EnumDirection enumdirection = (EnumDirection) iterator1.next(); ++ ++ this.a(generatoraccessseed, blockposition2.shift(enumdirection), (IBlockData) Blocks.WALL_TORCH.getBlockData().set(BlockTorchWall.a, enumdirection)); ++ } ++ ++ return true; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureBoundingBox.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureBoundingBox.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b5d6c8163c686c31375fb645d7721af06c01df40 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureBoundingBox.java +@@ -0,0 +1,160 @@ ++package net.minecraft.world.level.levelgen.structure; ++ ++import com.google.common.base.MoreObjects; ++import net.minecraft.core.BaseBlockPosition; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.nbt.NBTTagIntArray; ++ ++public class StructureBoundingBox { ++ ++ public int a; ++ public int b; ++ public int c; ++ public int d; ++ public int e; ++ public int f; ++ ++ public StructureBoundingBox() {} ++ ++ public StructureBoundingBox(int[] aint) { ++ if (aint.length == 6) { ++ this.a = aint[0]; ++ this.b = aint[1]; ++ this.c = aint[2]; ++ this.d = aint[3]; ++ this.e = aint[4]; ++ this.f = aint[5]; ++ } ++ ++ } ++ ++ public static StructureBoundingBox a() { ++ return new StructureBoundingBox(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE); ++ } ++ ++ public static StructureBoundingBox b() { ++ return new StructureBoundingBox(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); ++ } ++ ++ public static StructureBoundingBox a(int i, int j, int k, int l, int i1, int j1, int k1, int l1, int i2, EnumDirection enumdirection) { ++ switch (enumdirection) { ++ case NORTH: ++ return new StructureBoundingBox(i + l, j + i1, k - i2 + 1 + j1, i + k1 - 1 + l, j + l1 - 1 + i1, k + j1); ++ case SOUTH: ++ return new StructureBoundingBox(i + l, j + i1, k + j1, i + k1 - 1 + l, j + l1 - 1 + i1, k + i2 - 1 + j1); ++ case WEST: ++ return new StructureBoundingBox(i - i2 + 1 + j1, j + i1, k + l, i + j1, j + l1 - 1 + i1, k + k1 - 1 + l); ++ case EAST: ++ return new StructureBoundingBox(i + j1, j + i1, k + l, i + i2 - 1 + j1, j + l1 - 1 + i1, k + k1 - 1 + l); ++ default: ++ return new StructureBoundingBox(i + l, j + i1, k + j1, i + k1 - 1 + l, j + l1 - 1 + i1, k + i2 - 1 + j1); ++ } ++ } ++ ++ public static StructureBoundingBox a(int i, int j, int k, int l, int i1, int j1) { ++ return new StructureBoundingBox(Math.min(i, l), Math.min(j, i1), Math.min(k, j1), Math.max(i, l), Math.max(j, i1), Math.max(k, j1)); ++ } ++ ++ public StructureBoundingBox(StructureBoundingBox structureboundingbox) { ++ this.a = structureboundingbox.a; ++ this.b = structureboundingbox.b; ++ this.c = structureboundingbox.c; ++ this.d = structureboundingbox.d; ++ this.e = structureboundingbox.e; ++ this.f = structureboundingbox.f; ++ } ++ ++ public StructureBoundingBox(int i, int j, int k, int l, int i1, int j1) { ++ this.a = i; ++ this.b = j; ++ this.c = k; ++ this.d = l; ++ this.e = i1; ++ this.f = j1; ++ } ++ ++ public StructureBoundingBox(BaseBlockPosition baseblockposition, BaseBlockPosition baseblockposition1) { ++ this.a = Math.min(baseblockposition.getX(), baseblockposition1.getX()); ++ this.b = Math.min(baseblockposition.getY(), baseblockposition1.getY()); ++ this.c = Math.min(baseblockposition.getZ(), baseblockposition1.getZ()); ++ this.d = Math.max(baseblockposition.getX(), baseblockposition1.getX()); ++ this.e = Math.max(baseblockposition.getY(), baseblockposition1.getY()); ++ this.f = Math.max(baseblockposition.getZ(), baseblockposition1.getZ()); ++ } ++ ++ public StructureBoundingBox(int i, int j, int k, int l) { ++ this.a = i; ++ this.c = j; ++ this.d = k; ++ this.f = l; ++ this.b = 1; ++ this.e = 512; ++ } ++ ++ public boolean b(StructureBoundingBox structureboundingbox) { ++ return this.d >= structureboundingbox.a && this.a <= structureboundingbox.d && this.f >= structureboundingbox.c && this.c <= structureboundingbox.f && this.e >= structureboundingbox.b && this.b <= structureboundingbox.e; ++ } ++ ++ public boolean a(int i, int j, int k, int l) { ++ return this.d >= i && this.a <= k && this.f >= j && this.c <= l; ++ } ++ ++ public void c(StructureBoundingBox structureboundingbox) { ++ this.a = Math.min(this.a, structureboundingbox.a); ++ this.b = Math.min(this.b, structureboundingbox.b); ++ this.c = Math.min(this.c, structureboundingbox.c); ++ this.d = Math.max(this.d, structureboundingbox.d); ++ this.e = Math.max(this.e, structureboundingbox.e); ++ this.f = Math.max(this.f, structureboundingbox.f); ++ } ++ ++ public void a(int i, int j, int k) { ++ this.a += i; ++ this.b += j; ++ this.c += k; ++ this.d += i; ++ this.e += j; ++ this.f += k; ++ } ++ ++ public StructureBoundingBox b(int i, int j, int k) { ++ return new StructureBoundingBox(this.a + i, this.b + j, this.c + k, this.d + i, this.e + j, this.f + k); ++ } ++ ++ public void a(BaseBlockPosition baseblockposition) { ++ this.a(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); ++ } ++ ++ public boolean b(BaseBlockPosition baseblockposition) { ++ return baseblockposition.getX() >= this.a && baseblockposition.getX() <= this.d && baseblockposition.getZ() >= this.c && baseblockposition.getZ() <= this.f && baseblockposition.getY() >= this.b && baseblockposition.getY() <= this.e; ++ } ++ ++ public BaseBlockPosition c() { ++ return new BaseBlockPosition(this.d - this.a, this.e - this.b, this.f - this.c); ++ } ++ ++ public int d() { ++ return this.d - this.a + 1; ++ } ++ ++ public int e() { ++ return this.e - this.b + 1; ++ } ++ ++ public int f() { ++ return this.f - this.c + 1; ++ } ++ ++ public BaseBlockPosition g() { ++ return new BlockPosition(this.a + (this.d - this.a + 1) / 2, this.b + (this.e - this.b + 1) / 2, this.c + (this.f - this.c + 1) / 2); ++ } ++ ++ public String toString() { ++ return MoreObjects.toStringHelper(this).add("x0", this.a).add("y0", this.b).add("z0", this.c).add("x1", this.d).add("y1", this.e).add("z1", this.f).toString(); ++ } ++ ++ public NBTTagIntArray h() { ++ return new NBTTagIntArray(new int[]{this.a, this.b, this.c, this.d, this.e, this.f}); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNether.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNether.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9efe3a1dc5da760f0d8b0b39a10e642a53321aa5 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNether.java +@@ -0,0 +1,93 @@ ++package net.minecraft.world.level.levelgen.surfacebuilders; ++ ++import com.mojang.serialization.Codec; ++import java.util.Random; ++import java.util.stream.IntStream; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.world.level.biome.BiomeBase; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.levelgen.SeededRandom; ++import net.minecraft.world.level.levelgen.synth.NoiseGeneratorOctaves; ++ ++public class WorldGenSurfaceNether extends WorldGenSurface { ++ ++ private static final IBlockData c = Blocks.CAVE_AIR.getBlockData(); ++ private static final IBlockData d = Blocks.GRAVEL.getBlockData(); ++ private static final IBlockData e = Blocks.SOUL_SAND.getBlockData(); ++ protected long a; ++ protected NoiseGeneratorOctaves b; ++ ++ public WorldGenSurfaceNether(Codec codec) { ++ super(codec); ++ } ++ ++ public void a(Random random, IChunkAccess ichunkaccess, BiomeBase biomebase, int i, int j, int k, double d0, IBlockData iblockdata, IBlockData iblockdata1, int l, long i1, WorldGenSurfaceConfigurationBase worldgensurfaceconfigurationbase) { ++ int j1 = l; ++ int k1 = i & 15; ++ int l1 = j & 15; ++ double d1 = 0.03125D; ++ boolean flag = this.b.a((double) i * 0.03125D, (double) j * 0.03125D, 0.0D) * 75.0D + random.nextDouble() > 0.0D; ++ boolean flag1 = this.b.a((double) i * 0.03125D, 109.0D, (double) j * 0.03125D) * 75.0D + random.nextDouble() > 0.0D; ++ int i2 = (int) (d0 / 3.0D + 3.0D + random.nextDouble() * 0.25D); ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ int j2 = -1; ++ IBlockData iblockdata2 = worldgensurfaceconfigurationbase.a(); ++ IBlockData iblockdata3 = worldgensurfaceconfigurationbase.b(); ++ ++ for (int k2 = 127; k2 >= 0; --k2) { ++ blockposition_mutableblockposition.d(k1, k2, l1); ++ IBlockData iblockdata4 = ichunkaccess.getType(blockposition_mutableblockposition); ++ ++ if (iblockdata4.isAir()) { ++ j2 = -1; ++ } else if (iblockdata4.a(iblockdata.getBlock())) { ++ if (j2 == -1) { ++ boolean flag2 = false; ++ ++ if (i2 <= 0) { ++ flag2 = true; ++ iblockdata3 = worldgensurfaceconfigurationbase.b(); ++ } else if (k2 >= j1 - 4 && k2 <= j1 + 1) { ++ iblockdata2 = worldgensurfaceconfigurationbase.a(); ++ iblockdata3 = worldgensurfaceconfigurationbase.b(); ++ if (flag1) { ++ iblockdata2 = WorldGenSurfaceNether.d; ++ iblockdata3 = worldgensurfaceconfigurationbase.b(); ++ } ++ ++ if (flag) { ++ iblockdata2 = WorldGenSurfaceNether.e; ++ iblockdata3 = WorldGenSurfaceNether.e; ++ } ++ } ++ ++ if (k2 < j1 && flag2) { ++ iblockdata2 = iblockdata1; ++ } ++ ++ j2 = i2; ++ if (k2 >= j1 - 1) { ++ ichunkaccess.setType(blockposition_mutableblockposition, iblockdata2, false); ++ } else { ++ ichunkaccess.setType(blockposition_mutableblockposition, iblockdata3, false); ++ } ++ } else if (j2 > 0) { ++ --j2; ++ ichunkaccess.setType(blockposition_mutableblockposition, iblockdata3, false); ++ } ++ } ++ } ++ ++ } ++ ++ @Override ++ public void a(long i) { ++ if (this.a != i || this.b == null) { ++ this.b = new NoiseGeneratorOctaves(new SeededRandom(i), IntStream.rangeClosed(-3, 0)); ++ } ++ ++ this.a = i; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherAbstract.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherAbstract.java +new file mode 100644 +index 0000000000000000000000000000000000000000..22926296e66866c7fca13466004c20a16e94dc47 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherAbstract.java +@@ -0,0 +1,110 @@ ++package net.minecraft.world.level.levelgen.surfacebuilders; ++ ++import com.google.common.collect.ImmutableList; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.ImmutableMap.Builder; ++import com.google.common.collect.UnmodifiableIterator; ++import com.mojang.serialization.Codec; ++import java.util.Comparator; ++import java.util.Map.Entry; ++import java.util.Random; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.world.level.biome.BiomeBase; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.levelgen.SeededRandom; ++import net.minecraft.world.level.levelgen.synth.NoiseGeneratorOctaves; ++ ++public abstract class WorldGenSurfaceNetherAbstract extends WorldGenSurface { ++ ++ private long a; ++ private ImmutableMap b = ImmutableMap.of(); ++ private ImmutableMap c = ImmutableMap.of(); ++ private NoiseGeneratorOctaves d; ++ ++ public WorldGenSurfaceNetherAbstract(Codec codec) { ++ super(codec); ++ } ++ ++ public void a(Random random, IChunkAccess ichunkaccess, BiomeBase biomebase, int i, int j, int k, double d0, IBlockData iblockdata, IBlockData iblockdata1, int l, long i1, WorldGenSurfaceConfigurationBase worldgensurfaceconfigurationbase) { ++ int j1 = l + 1; ++ int k1 = i & 15; ++ int l1 = j & 15; ++ int i2 = (int) (d0 / 3.0D + 3.0D + random.nextDouble() * 0.25D); ++ int j2 = (int) (d0 / 3.0D + 3.0D + random.nextDouble() * 0.25D); ++ double d1 = 0.03125D; ++ boolean flag = this.d.a((double) i * 0.03125D, 109.0D, (double) j * 0.03125D) * 75.0D + random.nextDouble() > 0.0D; ++ IBlockData iblockdata2 = (IBlockData) ((Entry) this.c.entrySet().stream().max(Comparator.comparing((entry) -> { ++ return ((NoiseGeneratorOctaves) entry.getValue()).a((double) i, (double) l, (double) j); ++ })).get()).getKey(); ++ IBlockData iblockdata3 = (IBlockData) ((Entry) this.b.entrySet().stream().max(Comparator.comparing((entry) -> { ++ return ((NoiseGeneratorOctaves) entry.getValue()).a((double) i, (double) l, (double) j); ++ })).get()).getKey(); ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ IBlockData iblockdata4 = ichunkaccess.getType(blockposition_mutableblockposition.d(k1, 128, l1)); ++ ++ for (int k2 = 127; k2 >= 0; --k2) { ++ blockposition_mutableblockposition.d(k1, k2, l1); ++ IBlockData iblockdata5 = ichunkaccess.getType(blockposition_mutableblockposition); ++ int l2; ++ ++ if (iblockdata4.a(iblockdata.getBlock()) && (iblockdata5.isAir() || iblockdata5 == iblockdata1)) { ++ for (l2 = 0; l2 < i2; ++l2) { ++ blockposition_mutableblockposition.c(EnumDirection.UP); ++ if (!ichunkaccess.getType(blockposition_mutableblockposition).a(iblockdata.getBlock())) { ++ break; ++ } ++ ++ ichunkaccess.setType(blockposition_mutableblockposition, iblockdata2, false); ++ } ++ ++ blockposition_mutableblockposition.d(k1, k2, l1); ++ } ++ ++ if ((iblockdata4.isAir() || iblockdata4 == iblockdata1) && iblockdata5.a(iblockdata.getBlock())) { ++ for (l2 = 0; l2 < j2 && ichunkaccess.getType(blockposition_mutableblockposition).a(iblockdata.getBlock()); ++l2) { ++ if (flag && k2 >= j1 - 4 && k2 <= j1 + 1) { ++ ichunkaccess.setType(blockposition_mutableblockposition, this.c(), false); ++ } else { ++ ichunkaccess.setType(blockposition_mutableblockposition, iblockdata3, false); ++ } ++ ++ blockposition_mutableblockposition.c(EnumDirection.DOWN); ++ } ++ } ++ ++ iblockdata4 = iblockdata5; ++ } ++ ++ } ++ ++ @Override ++ public void a(long i) { ++ if (this.a != i || this.d == null || this.b.isEmpty() || this.c.isEmpty()) { ++ this.b = a(this.a(), i); ++ this.c = a(this.b(), i + (long) this.b.size()); ++ this.d = new NoiseGeneratorOctaves(new SeededRandom(i + (long) this.b.size() + (long) this.c.size()), ImmutableList.of(0)); ++ } ++ ++ this.a = i; ++ } ++ ++ private static ImmutableMap a(ImmutableList immutablelist, long i) { ++ Builder builder = new Builder(); ++ ++ for (UnmodifiableIterator unmodifiableiterator = immutablelist.iterator(); unmodifiableiterator.hasNext(); ++i) { ++ IBlockData iblockdata = (IBlockData) unmodifiableiterator.next(); ++ ++ builder.put(iblockdata, new NoiseGeneratorOctaves(new SeededRandom(i), ImmutableList.of(-4))); ++ } ++ ++ return builder.build(); ++ } ++ ++ protected abstract ImmutableList a(); ++ ++ protected abstract ImmutableList b(); ++ ++ protected abstract IBlockData c(); ++} +diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherForest.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherForest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3bd78b0fc75a536e4e37f5ac67843ff778cd1b5f +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherForest.java +@@ -0,0 +1,86 @@ ++package net.minecraft.world.level.levelgen.surfacebuilders; ++ ++import com.google.common.collect.ImmutableList; ++import com.mojang.serialization.Codec; ++import java.util.Random; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.world.level.biome.BiomeBase; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.levelgen.SeededRandom; ++import net.minecraft.world.level.levelgen.synth.NoiseGeneratorOctaves; ++ ++public class WorldGenSurfaceNetherForest extends WorldGenSurface { ++ ++ private static final IBlockData b = Blocks.CAVE_AIR.getBlockData(); ++ protected long a; ++ private NoiseGeneratorOctaves c; ++ ++ public WorldGenSurfaceNetherForest(Codec codec) { ++ super(codec); ++ } ++ ++ public void a(Random random, IChunkAccess ichunkaccess, BiomeBase biomebase, int i, int j, int k, double d0, IBlockData iblockdata, IBlockData iblockdata1, int l, long i1, WorldGenSurfaceConfigurationBase worldgensurfaceconfigurationbase) { ++ int j1 = l; ++ int k1 = i & 15; ++ int l1 = j & 15; ++ double d1 = this.c.a((double) i * 0.1D, (double) l, (double) j * 0.1D); ++ boolean flag = d1 > 0.15D + random.nextDouble() * 0.35D; ++ double d2 = this.c.a((double) i * 0.1D, 109.0D, (double) j * 0.1D); ++ boolean flag1 = d2 > 0.25D + random.nextDouble() * 0.9D; ++ int i2 = (int) (d0 / 3.0D + 3.0D + random.nextDouble() * 0.25D); ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ int j2 = -1; ++ IBlockData iblockdata2 = worldgensurfaceconfigurationbase.b(); ++ ++ for (int k2 = 127; k2 >= 0; --k2) { ++ blockposition_mutableblockposition.d(k1, k2, l1); ++ IBlockData iblockdata3 = worldgensurfaceconfigurationbase.a(); ++ IBlockData iblockdata4 = ichunkaccess.getType(blockposition_mutableblockposition); ++ ++ if (iblockdata4.isAir()) { ++ j2 = -1; ++ } else if (iblockdata4.a(iblockdata.getBlock())) { ++ if (j2 == -1) { ++ boolean flag2 = false; ++ ++ if (i2 <= 0) { ++ flag2 = true; ++ iblockdata2 = worldgensurfaceconfigurationbase.b(); ++ } ++ ++ if (flag) { ++ iblockdata3 = worldgensurfaceconfigurationbase.b(); ++ } else if (flag1) { ++ iblockdata3 = worldgensurfaceconfigurationbase.c(); ++ } ++ ++ if (k2 < j1 && flag2) { ++ iblockdata3 = iblockdata1; ++ } ++ ++ j2 = i2; ++ if (k2 >= j1 - 1) { ++ ichunkaccess.setType(blockposition_mutableblockposition, iblockdata3, false); ++ } else { ++ ichunkaccess.setType(blockposition_mutableblockposition, iblockdata2, false); ++ } ++ } else if (j2 > 0) { ++ --j2; ++ ichunkaccess.setType(blockposition_mutableblockposition, iblockdata2, false); ++ } ++ } ++ } ++ ++ } ++ ++ @Override ++ public void a(long i) { ++ if (this.a != i || this.c == null) { ++ this.c = new NoiseGeneratorOctaves(new SeededRandom(i), ImmutableList.of(0)); ++ } ++ ++ this.a = i; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineBlock.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineBlock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2a0a5a70c795ba33780c8db774eaf9769a85daa7 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineBlock.java +@@ -0,0 +1,141 @@ ++package net.minecraft.world.level.lighting; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.ILightAccess; ++import net.minecraft.world.level.chunk.NibbleArray; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapes; ++import org.apache.commons.lang3.mutable.MutableInt; ++ ++public final class LightEngineBlock extends LightEngineLayer { ++ ++ private static final EnumDirection[] e = EnumDirection.values(); ++ private final BlockPosition.MutableBlockPosition f = new BlockPosition.MutableBlockPosition(); ++ ++ public LightEngineBlock(ILightAccess ilightaccess) { ++ super(ilightaccess, EnumSkyBlock.BLOCK, new LightEngineStorageBlock(ilightaccess)); ++ } ++ ++ private int d(long i) { ++ int j = BlockPosition.b(i); ++ int k = BlockPosition.c(i); ++ int l = BlockPosition.d(i); ++ IBlockAccess iblockaccess = this.a.c(j >> 4, l >> 4); ++ ++ return iblockaccess != null ? iblockaccess.g(this.f.d(j, k, l)) : 0; ++ } ++ ++ @Override ++ protected int b(long i, long j, int k) { ++ if (j == Long.MAX_VALUE) { ++ return 15; ++ } else if (i == Long.MAX_VALUE) { ++ return k + 15 - this.d(j); ++ } else if (k >= 15) { ++ return k; ++ } else { ++ int l = Integer.signum(BlockPosition.b(j) - BlockPosition.b(i)); ++ int i1 = Integer.signum(BlockPosition.c(j) - BlockPosition.c(i)); ++ int j1 = Integer.signum(BlockPosition.d(j) - BlockPosition.d(i)); ++ EnumDirection enumdirection = EnumDirection.a(l, i1, j1); ++ ++ if (enumdirection == null) { ++ return 15; ++ } else { ++ MutableInt mutableint = new MutableInt(); ++ IBlockData iblockdata = this.a(j, mutableint); ++ ++ if (mutableint.getValue() >= 15) { ++ return 15; ++ } else { ++ IBlockData iblockdata1 = this.a(i, (MutableInt) null); ++ VoxelShape voxelshape = this.a(iblockdata1, i, enumdirection); ++ VoxelShape voxelshape1 = this.a(iblockdata, j, enumdirection.opposite()); ++ ++ return VoxelShapes.b(voxelshape, voxelshape1) ? 15 : k + Math.max(1, mutableint.getValue()); ++ } ++ } ++ } ++ } ++ ++ @Override ++ protected void a(long i, int j, boolean flag) { ++ long k = SectionPosition.e(i); ++ EnumDirection[] aenumdirection = LightEngineBlock.e; ++ int l = aenumdirection.length; ++ ++ for (int i1 = 0; i1 < l; ++i1) { ++ EnumDirection enumdirection = aenumdirection[i1]; ++ long j1 = BlockPosition.a(i, enumdirection); ++ long k1 = SectionPosition.e(j1); ++ ++ if (k == k1 || ((LightEngineStorageBlock) this.c).g(k1)) { ++ this.b(i, j1, j, flag); ++ } ++ } ++ ++ } ++ ++ @Override ++ protected int a(long i, long j, int k) { ++ int l = k; ++ ++ if (Long.MAX_VALUE != j) { ++ int i1 = this.b(Long.MAX_VALUE, i, 0); ++ ++ if (k > i1) { ++ l = i1; ++ } ++ ++ if (l == 0) { ++ return l; ++ } ++ } ++ ++ long j1 = SectionPosition.e(i); ++ NibbleArray nibblearray = ((LightEngineStorageBlock) this.c).a(j1, true); ++ EnumDirection[] aenumdirection = LightEngineBlock.e; ++ int k1 = aenumdirection.length; ++ ++ for (int l1 = 0; l1 < k1; ++l1) { ++ EnumDirection enumdirection = aenumdirection[l1]; ++ long i2 = BlockPosition.a(i, enumdirection); ++ ++ if (i2 != j) { ++ long j2 = SectionPosition.e(i2); ++ NibbleArray nibblearray1; ++ ++ if (j1 == j2) { ++ nibblearray1 = nibblearray; ++ } else { ++ nibblearray1 = ((LightEngineStorageBlock) this.c).a(j2, true); ++ } ++ ++ if (nibblearray1 != null) { ++ int k2 = this.b(i2, i, this.a(nibblearray1, i2)); ++ ++ if (l > k2) { ++ l = k2; ++ } ++ ++ if (l == 0) { ++ return l; ++ } ++ } ++ } ++ } ++ ++ return l; ++ } ++ ++ @Override ++ public void a(BlockPosition blockposition, int i) { ++ ((LightEngineStorageBlock) this.c).d(); ++ this.a(Long.MAX_VALUE, blockposition.asLong(), 15 - i, true); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineLayer.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineLayer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..944a8c295ff9df0d96800ddc4f6763598cf61d0d +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineLayer.java +@@ -0,0 +1,231 @@ ++package net.minecraft.world.level.lighting; ++ ++import java.util.Arrays; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.ILightAccess; ++import net.minecraft.world.level.chunk.NibbleArray; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapes; ++import org.apache.commons.lang3.mutable.MutableInt; ++ ++public abstract class LightEngineLayer, S extends LightEngineStorage> extends LightEngineGraph implements LightEngineLayerEventListener { ++ ++ private static final EnumDirection[] e = EnumDirection.values(); ++ protected final ILightAccess a; ++ protected final EnumSkyBlock b; ++ protected final S c; ++ private boolean f; ++ protected final BlockPosition.MutableBlockPosition d = new BlockPosition.MutableBlockPosition(); ++ private final long[] g = new long[2]; ++ private final IBlockAccess[] h = new IBlockAccess[2]; ++ ++ public LightEngineLayer(ILightAccess ilightaccess, EnumSkyBlock enumskyblock, S s0) { ++ super(16, 256, 8192); ++ this.a = ilightaccess; ++ this.b = enumskyblock; ++ this.c = s0; ++ this.d(); ++ } ++ ++ @Override ++ protected void f(long i) { ++ this.c.d(); ++ if (this.c.g(SectionPosition.e(i))) { ++ super.f(i); ++ } ++ ++ } ++ ++ @Nullable ++ private IBlockAccess a(int i, int j) { ++ long k = ChunkCoordIntPair.pair(i, j); ++ ++ for (int l = 0; l < 2; ++l) { ++ if (k == this.g[l]) { ++ return this.h[l]; ++ } ++ } ++ ++ IBlockAccess iblockaccess = this.a.c(i, j); ++ ++ for (int i1 = 1; i1 > 0; --i1) { ++ this.g[i1] = this.g[i1 - 1]; ++ this.h[i1] = this.h[i1 - 1]; ++ } ++ ++ this.g[0] = k; ++ this.h[0] = iblockaccess; ++ return iblockaccess; ++ } ++ ++ private void d() { ++ Arrays.fill(this.g, ChunkCoordIntPair.a); ++ Arrays.fill(this.h, (Object) null); ++ } ++ ++ protected IBlockData a(long i, @Nullable MutableInt mutableint) { ++ if (i == Long.MAX_VALUE) { ++ if (mutableint != null) { ++ mutableint.setValue(0); ++ } ++ ++ return Blocks.AIR.getBlockData(); ++ } else { ++ int j = SectionPosition.a(BlockPosition.b(i)); ++ int k = SectionPosition.a(BlockPosition.d(i)); ++ IBlockAccess iblockaccess = this.a(j, k); ++ ++ if (iblockaccess == null) { ++ if (mutableint != null) { ++ mutableint.setValue(16); ++ } ++ ++ return Blocks.BEDROCK.getBlockData(); ++ } else { ++ this.d.g(i); ++ IBlockData iblockdata = iblockaccess.getType(this.d); ++ boolean flag = iblockdata.l() && iblockdata.e(); ++ ++ if (mutableint != null) { ++ mutableint.setValue(iblockdata.b(this.a.getWorld(), (BlockPosition) this.d)); ++ } ++ ++ return flag ? iblockdata : Blocks.AIR.getBlockData(); ++ } ++ } ++ } ++ ++ protected VoxelShape a(IBlockData iblockdata, long i, EnumDirection enumdirection) { ++ return iblockdata.l() ? iblockdata.a(this.a.getWorld(), this.d.g(i), enumdirection) : VoxelShapes.a(); ++ } ++ ++ public static int a(IBlockAccess iblockaccess, IBlockData iblockdata, BlockPosition blockposition, IBlockData iblockdata1, BlockPosition blockposition1, EnumDirection enumdirection, int i) { ++ boolean flag = iblockdata.l() && iblockdata.e(); ++ boolean flag1 = iblockdata1.l() && iblockdata1.e(); ++ ++ if (!flag && !flag1) { ++ return i; ++ } else { ++ VoxelShape voxelshape = flag ? iblockdata.c(iblockaccess, blockposition) : VoxelShapes.a(); ++ VoxelShape voxelshape1 = flag1 ? iblockdata1.c(iblockaccess, blockposition1) : VoxelShapes.a(); ++ ++ return VoxelShapes.b(voxelshape, voxelshape1, enumdirection) ? 16 : i; ++ } ++ } ++ ++ @Override ++ protected boolean a(long i) { ++ return i == Long.MAX_VALUE; ++ } ++ ++ @Override ++ protected int a(long i, long j, int k) { ++ return 0; ++ } ++ ++ @Override ++ protected int c(long i) { ++ return i == Long.MAX_VALUE ? 0 : 15 - this.c.i(i); ++ } ++ ++ protected int a(NibbleArray nibblearray, long i) { ++ return 15 - nibblearray.a(SectionPosition.b(BlockPosition.b(i)), SectionPosition.b(BlockPosition.c(i)), SectionPosition.b(BlockPosition.d(i))); ++ } ++ ++ @Override ++ protected void a(long i, int j) { ++ this.c.b(i, Math.min(15, 15 - j)); ++ } ++ ++ @Override ++ protected int b(long i, long j, int k) { ++ return 0; ++ } ++ ++ public boolean a() { ++ return this.b() || this.c.b() || this.c.a(); ++ } ++ ++ public int a(int i, boolean flag, boolean flag1) { ++ if (!this.f) { ++ if (this.c.b()) { ++ i = this.c.b(i); ++ if (i == 0) { ++ return i; ++ } ++ } ++ ++ this.c.a(this, flag, flag1); ++ } ++ ++ this.f = true; ++ if (this.b()) { ++ i = this.b(i); ++ this.d(); ++ if (i == 0) { ++ return i; ++ } ++ } ++ ++ this.f = false; ++ this.c.e(); ++ return i; ++ } ++ ++ protected void a(long i, @Nullable NibbleArray nibblearray, boolean flag) { ++ this.c.a(i, nibblearray, flag); ++ } ++ ++ @Nullable ++ @Override ++ public NibbleArray a(SectionPosition sectionposition) { ++ return this.c.h(sectionposition.s()); ++ } ++ ++ @Override ++ public int b(BlockPosition blockposition) { ++ return this.c.d(blockposition.asLong()); ++ } ++ ++ public void a(BlockPosition blockposition) { ++ long i = blockposition.asLong(); ++ ++ this.f(i); ++ EnumDirection[] aenumdirection = LightEngineLayer.e; ++ int j = aenumdirection.length; ++ ++ for (int k = 0; k < j; ++k) { ++ EnumDirection enumdirection = aenumdirection[k]; ++ ++ this.f(BlockPosition.a(i, enumdirection)); ++ } ++ ++ } ++ ++ public void a(BlockPosition blockposition, int i) {} ++ ++ @Override ++ public void a(SectionPosition sectionposition, boolean flag) { ++ this.c.d(sectionposition.s(), flag); ++ } ++ ++ public void a(ChunkCoordIntPair chunkcoordintpair, boolean flag) { ++ long i = SectionPosition.f(SectionPosition.b(chunkcoordintpair.x, 0, chunkcoordintpair.z)); ++ ++ this.c.b(i, flag); ++ } ++ ++ public void b(ChunkCoordIntPair chunkcoordintpair, boolean flag) { ++ long i = SectionPosition.f(SectionPosition.b(chunkcoordintpair.x, 0, chunkcoordintpair.z)); ++ ++ this.c.c(i, flag); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineSky.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineSky.java +new file mode 100644 +index 0000000000000000000000000000000000000000..113c575a16121aa1146f21a6f41ebd9d12a0c924 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineSky.java +@@ -0,0 +1,260 @@ ++package net.minecraft.world.level.lighting; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.ILightAccess; ++import net.minecraft.world.level.chunk.NibbleArray; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapes; ++import org.apache.commons.lang3.mutable.MutableInt; ++ ++public final class LightEngineSky extends LightEngineLayer { ++ ++ private static final EnumDirection[] e = EnumDirection.values(); ++ private static final EnumDirection[] f = new EnumDirection[]{EnumDirection.NORTH, EnumDirection.SOUTH, EnumDirection.WEST, EnumDirection.EAST}; ++ ++ public LightEngineSky(ILightAccess ilightaccess) { ++ super(ilightaccess, EnumSkyBlock.SKY, new LightEngineStorageSky(ilightaccess)); ++ } ++ ++ @Override ++ protected int b(long i, long j, int k) { ++ if (j == Long.MAX_VALUE) { ++ return 15; ++ } else { ++ if (i == Long.MAX_VALUE) { ++ if (!((LightEngineStorageSky) this.c).m(j)) { ++ return 15; ++ } ++ ++ k = 0; ++ } ++ ++ if (k >= 15) { ++ return k; ++ } else { ++ MutableInt mutableint = new MutableInt(); ++ IBlockData iblockdata = this.a(j, mutableint); ++ ++ if (mutableint.getValue() >= 15) { ++ return 15; ++ } else { ++ int l = BlockPosition.b(i); ++ int i1 = BlockPosition.c(i); ++ int j1 = BlockPosition.d(i); ++ int k1 = BlockPosition.b(j); ++ int l1 = BlockPosition.c(j); ++ int i2 = BlockPosition.d(j); ++ boolean flag = l == k1 && j1 == i2; ++ int j2 = Integer.signum(k1 - l); ++ int k2 = Integer.signum(l1 - i1); ++ int l2 = Integer.signum(i2 - j1); ++ EnumDirection enumdirection; ++ ++ if (i == Long.MAX_VALUE) { ++ enumdirection = EnumDirection.DOWN; ++ } else { ++ enumdirection = EnumDirection.a(j2, k2, l2); ++ } ++ ++ IBlockData iblockdata1 = this.a(i, (MutableInt) null); ++ VoxelShape voxelshape; ++ ++ if (enumdirection != null) { ++ voxelshape = this.a(iblockdata1, i, enumdirection); ++ VoxelShape voxelshape1 = this.a(iblockdata, j, enumdirection.opposite()); ++ ++ if (VoxelShapes.b(voxelshape, voxelshape1)) { ++ return 15; ++ } ++ } else { ++ voxelshape = this.a(iblockdata1, i, EnumDirection.DOWN); ++ if (VoxelShapes.b(voxelshape, VoxelShapes.a())) { ++ return 15; ++ } ++ ++ int i3 = flag ? -1 : 0; ++ EnumDirection enumdirection1 = EnumDirection.a(j2, i3, l2); ++ ++ if (enumdirection1 == null) { ++ return 15; ++ } ++ ++ VoxelShape voxelshape2 = this.a(iblockdata, j, enumdirection1.opposite()); ++ ++ if (VoxelShapes.b(VoxelShapes.a(), voxelshape2)) { ++ return 15; ++ } ++ } ++ ++ boolean flag1 = i == Long.MAX_VALUE || flag && i1 > l1; ++ ++ return flag1 && k == 0 && mutableint.getValue() == 0 ? 0 : k + Math.max(1, mutableint.getValue()); ++ } ++ } ++ } ++ } ++ ++ @Override ++ protected void a(long i, int j, boolean flag) { ++ long k = SectionPosition.e(i); ++ int l = BlockPosition.c(i); ++ int i1 = SectionPosition.b(l); ++ int j1 = SectionPosition.a(l); ++ int k1; ++ ++ if (i1 != 0) { ++ k1 = 0; ++ } else { ++ int l1; ++ ++ for (l1 = 0; !((LightEngineStorageSky) this.c).g(SectionPosition.a(k, 0, -l1 - 1, 0)) && ((LightEngineStorageSky) this.c).a(j1 - l1 - 1); ++l1) { ++ ; ++ } ++ ++ k1 = l1; ++ } ++ ++ long i2 = BlockPosition.a(i, 0, -1 - k1 * 16, 0); ++ long j2 = SectionPosition.e(i2); ++ ++ if (k == j2 || ((LightEngineStorageSky) this.c).g(j2)) { ++ this.b(i, i2, j, flag); ++ } ++ ++ long k2 = BlockPosition.a(i, EnumDirection.UP); ++ long l2 = SectionPosition.e(k2); ++ ++ if (k == l2 || ((LightEngineStorageSky) this.c).g(l2)) { ++ this.b(i, k2, j, flag); ++ } ++ ++ EnumDirection[] aenumdirection = LightEngineSky.f; ++ int i3 = aenumdirection.length; ++ int j3 = 0; ++ ++ while (j3 < i3) { ++ EnumDirection enumdirection = aenumdirection[j3]; ++ int k3 = 0; ++ ++ while (true) { ++ long l3 = BlockPosition.a(i, enumdirection.getAdjacentX(), -k3, enumdirection.getAdjacentZ()); ++ long i4 = SectionPosition.e(l3); ++ ++ if (k == i4) { ++ this.b(i, l3, j, flag); ++ } else { ++ if (((LightEngineStorageSky) this.c).g(i4)) { ++ this.b(i, l3, j, flag); ++ } ++ ++ ++k3; ++ if (k3 <= k1 * 16) { ++ continue; ++ } ++ } ++ ++ ++j3; ++ break; ++ } ++ } ++ ++ } ++ ++ @Override ++ protected int a(long i, long j, int k) { ++ int l = k; ++ ++ if (Long.MAX_VALUE != j) { ++ int i1 = this.b(Long.MAX_VALUE, i, 0); ++ ++ if (k > i1) { ++ l = i1; ++ } ++ ++ if (l == 0) { ++ return l; ++ } ++ } ++ ++ long j1 = SectionPosition.e(i); ++ NibbleArray nibblearray = ((LightEngineStorageSky) this.c).a(j1, true); ++ EnumDirection[] aenumdirection = LightEngineSky.e; ++ int k1 = aenumdirection.length; ++ ++ for (int l1 = 0; l1 < k1; ++l1) { ++ EnumDirection enumdirection = aenumdirection[l1]; ++ long i2 = BlockPosition.a(i, enumdirection); ++ long j2 = SectionPosition.e(i2); ++ NibbleArray nibblearray1; ++ ++ if (j1 == j2) { ++ nibblearray1 = nibblearray; ++ } else { ++ nibblearray1 = ((LightEngineStorageSky) this.c).a(j2, true); ++ } ++ ++ if (nibblearray1 != null) { ++ if (i2 != j) { ++ int k2 = this.b(i2, i, this.a(nibblearray1, i2)); ++ ++ if (l > k2) { ++ l = k2; ++ } ++ ++ if (l == 0) { ++ return l; ++ } ++ } ++ } else if (enumdirection != EnumDirection.DOWN) { ++ for (i2 = BlockPosition.f(i2); !((LightEngineStorageSky) this.c).g(j2) && !((LightEngineStorageSky) this.c).n(j2); i2 = BlockPosition.a(i2, 0, 16, 0)) { ++ j2 = SectionPosition.a(j2, EnumDirection.UP); ++ } ++ ++ NibbleArray nibblearray2 = ((LightEngineStorageSky) this.c).a(j2, true); ++ ++ if (i2 != j) { ++ int l2; ++ ++ if (nibblearray2 != null) { ++ l2 = this.b(i2, i, this.a(nibblearray2, i2)); ++ } else { ++ l2 = ((LightEngineStorageSky) this.c).o(j2) ? 0 : 15; ++ } ++ ++ if (l > l2) { ++ l = l2; ++ } ++ ++ if (l == 0) { ++ return l; ++ } ++ } ++ } ++ } ++ ++ return l; ++ } ++ ++ @Override ++ protected void f(long i) { ++ ((LightEngineStorageSky) this.c).d(); ++ long j = SectionPosition.e(i); ++ ++ if (((LightEngineStorageSky) this.c).g(j)) { ++ super.f(i); ++ } else { ++ for (i = BlockPosition.f(i); !((LightEngineStorageSky) this.c).g(j) && !((LightEngineStorageSky) this.c).n(j); i = BlockPosition.a(i, 0, 16, 0)) { ++ j = SectionPosition.a(j, EnumDirection.UP); ++ } ++ ++ if (((LightEngineStorageSky) this.c).g(j)) { ++ super.f(i); ++ } ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5f3d2c090d098834e38e447d93f1ea8184c8fb3e +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java +@@ -0,0 +1,387 @@ ++package net.minecraft.world.level.lighting; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.longs.LongOpenHashSet; ++import it.unimi.dsi.fastutil.longs.LongSet; ++import it.unimi.dsi.fastutil.objects.ObjectIterator; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.server.level.LightEngineGraphSection; ++import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.chunk.ILightAccess; ++import net.minecraft.world.level.chunk.NibbleArray; ++ ++public abstract class LightEngineStorage> extends LightEngineGraphSection { ++ ++ protected static final NibbleArray a = new NibbleArray(); ++ private static final EnumDirection[] k = EnumDirection.values(); ++ private final EnumSkyBlock l; ++ private final ILightAccess m; ++ protected final LongSet b = new LongOpenHashSet(); ++ protected final LongSet c = new LongOpenHashSet(); ++ protected final LongSet d = new LongOpenHashSet(); ++ protected volatile M e; ++ protected final M f; ++ protected final LongSet g = new LongOpenHashSet(); ++ protected final LongSet h = new LongOpenHashSet(); ++ protected final Long2ObjectMap i = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap()); ++ private final LongSet n = new LongOpenHashSet(); ++ private final LongSet o = new LongOpenHashSet(); ++ private final LongSet p = new LongOpenHashSet(); ++ protected volatile boolean j; ++ ++ protected LightEngineStorage(EnumSkyBlock enumskyblock, ILightAccess ilightaccess, M m0) { ++ super(3, 16, 256); ++ this.l = enumskyblock; ++ this.m = ilightaccess; ++ this.f = m0; ++ this.e = m0.b(); ++ this.e.d(); ++ } ++ ++ protected boolean g(long i) { ++ return this.a(i, true) != null; ++ } ++ ++ @Nullable ++ protected NibbleArray a(long i, boolean flag) { ++ return this.a(flag ? this.f : this.e, i); ++ } ++ ++ @Nullable ++ protected NibbleArray a(M m0, long i) { ++ return m0.c(i); ++ } ++ ++ @Nullable ++ public NibbleArray h(long i) { ++ NibbleArray nibblearray = (NibbleArray) this.i.get(i); ++ ++ return nibblearray != null ? nibblearray : this.a(i, false); ++ } ++ ++ protected abstract int d(long i); ++ ++ protected int i(long i) { ++ long j = SectionPosition.e(i); ++ NibbleArray nibblearray = this.a(j, true); ++ ++ return nibblearray.a(SectionPosition.b(BlockPosition.b(i)), SectionPosition.b(BlockPosition.c(i)), SectionPosition.b(BlockPosition.d(i))); ++ } ++ ++ protected void b(long i, int j) { ++ long k = SectionPosition.e(i); ++ ++ if (this.g.add(k)) { ++ this.f.a(k); ++ } ++ ++ NibbleArray nibblearray = this.a(k, true); ++ ++ nibblearray.a(SectionPosition.b(BlockPosition.b(i)), SectionPosition.b(BlockPosition.c(i)), SectionPosition.b(BlockPosition.d(i)), j); ++ ++ for (int l = -1; l <= 1; ++l) { ++ for (int i1 = -1; i1 <= 1; ++i1) { ++ for (int j1 = -1; j1 <= 1; ++j1) { ++ this.h.add(SectionPosition.e(BlockPosition.a(i, i1, j1, l))); ++ } ++ } ++ } ++ ++ } ++ ++ @Override ++ protected int c(long i) { ++ return i == Long.MAX_VALUE ? 2 : (this.b.contains(i) ? 0 : (!this.p.contains(i) && this.f.b(i) ? 1 : 2)); ++ } ++ ++ @Override ++ protected int b(long i) { ++ return this.c.contains(i) ? 2 : (!this.b.contains(i) && !this.d.contains(i) ? 2 : 0); ++ } ++ ++ @Override ++ protected void a(long i, int j) { ++ int k = this.c(i); ++ ++ if (k != 0 && j == 0) { ++ this.b.add(i); ++ this.d.remove(i); ++ } ++ ++ if (k == 0 && j != 0) { ++ this.b.remove(i); ++ this.c.remove(i); ++ } ++ ++ if (k >= 2 && j != 2) { ++ if (this.p.contains(i)) { ++ this.p.remove(i); ++ } else { ++ this.f.a(i, this.j(i)); ++ this.g.add(i); ++ this.k(i); ++ ++ for (int l = -1; l <= 1; ++l) { ++ for (int i1 = -1; i1 <= 1; ++i1) { ++ for (int j1 = -1; j1 <= 1; ++j1) { ++ this.h.add(SectionPosition.e(BlockPosition.a(i, i1, j1, l))); ++ } ++ } ++ } ++ } ++ } ++ ++ if (k != 2 && j >= 2) { ++ this.p.add(i); ++ } ++ ++ this.j = !this.p.isEmpty(); ++ } ++ ++ protected NibbleArray j(long i) { ++ NibbleArray nibblearray = (NibbleArray) this.i.get(i); ++ ++ return nibblearray != null ? nibblearray : new NibbleArray(); ++ } ++ ++ protected void a(LightEngineLayer lightenginelayer, long i) { ++ if (lightenginelayer.c() < 8192) { ++ lightenginelayer.a((j) -> { ++ return SectionPosition.e(j) == i; ++ }); ++ } else { ++ int j = SectionPosition.c(SectionPosition.b(i)); ++ int k = SectionPosition.c(SectionPosition.c(i)); ++ int l = SectionPosition.c(SectionPosition.d(i)); ++ ++ for (int i1 = 0; i1 < 16; ++i1) { ++ for (int j1 = 0; j1 < 16; ++j1) { ++ for (int k1 = 0; k1 < 16; ++k1) { ++ long l1 = BlockPosition.a(j + i1, k + j1, l + k1); ++ ++ lightenginelayer.e(l1); ++ } ++ } ++ } ++ ++ } ++ } ++ ++ protected boolean a() { ++ return this.j; ++ } ++ ++ protected void a(LightEngineLayer lightenginelayer, boolean flag, boolean flag1) { ++ if (this.a() || !this.i.isEmpty()) { ++ LongIterator longiterator = this.p.iterator(); ++ ++ long i; ++ NibbleArray nibblearray; ++ ++ while (longiterator.hasNext()) { ++ i = (Long) longiterator.next(); ++ this.a(lightenginelayer, i); ++ NibbleArray nibblearray1 = (NibbleArray) this.i.remove(i); ++ ++ nibblearray = this.f.d(i); ++ if (this.o.contains(SectionPosition.f(i))) { ++ if (nibblearray1 != null) { ++ this.i.put(i, nibblearray1); ++ } else if (nibblearray != null) { ++ this.i.put(i, nibblearray); ++ } ++ } ++ } ++ ++ this.f.c(); ++ longiterator = this.p.iterator(); ++ ++ while (longiterator.hasNext()) { ++ i = (Long) longiterator.next(); ++ this.l(i); ++ } ++ ++ this.p.clear(); ++ this.j = false; ++ ObjectIterator objectiterator = this.i.long2ObjectEntrySet().iterator(); ++ ++ Entry entry; ++ long j; ++ ++ while (objectiterator.hasNext()) { ++ entry = (Entry) objectiterator.next(); ++ j = entry.getLongKey(); ++ if (this.g(j)) { ++ nibblearray = (NibbleArray) entry.getValue(); ++ if (this.f.c(j) != nibblearray) { ++ this.a(lightenginelayer, j); ++ this.f.a(j, nibblearray); ++ this.g.add(j); ++ } ++ } ++ } ++ ++ this.f.c(); ++ if (!flag1) { ++ longiterator = this.i.keySet().iterator(); ++ ++ while (longiterator.hasNext()) { ++ i = (Long) longiterator.next(); ++ this.b(lightenginelayer, i); ++ } ++ } else { ++ longiterator = this.n.iterator(); ++ ++ while (longiterator.hasNext()) { ++ i = (Long) longiterator.next(); ++ this.b(lightenginelayer, i); ++ } ++ } ++ ++ this.n.clear(); ++ objectiterator = this.i.long2ObjectEntrySet().iterator(); ++ ++ while (objectiterator.hasNext()) { ++ entry = (Entry) objectiterator.next(); ++ j = entry.getLongKey(); ++ if (this.g(j)) { ++ objectiterator.remove(); ++ } ++ } ++ ++ } ++ } ++ ++ private void b(LightEngineLayer lightenginelayer, long i) { ++ if (this.g(i)) { ++ int j = SectionPosition.c(SectionPosition.b(i)); ++ int k = SectionPosition.c(SectionPosition.c(i)); ++ int l = SectionPosition.c(SectionPosition.d(i)); ++ EnumDirection[] aenumdirection = LightEngineStorage.k; ++ int i1 = aenumdirection.length; ++ ++ for (int j1 = 0; j1 < i1; ++j1) { ++ EnumDirection enumdirection = aenumdirection[j1]; ++ long k1 = SectionPosition.a(i, enumdirection); ++ ++ if (!this.i.containsKey(k1) && this.g(k1)) { ++ for (int l1 = 0; l1 < 16; ++l1) { ++ for (int i2 = 0; i2 < 16; ++i2) { ++ long j2; ++ long k2; ++ ++ switch (enumdirection) { ++ case DOWN: ++ j2 = BlockPosition.a(j + i2, k, l + l1); ++ k2 = BlockPosition.a(j + i2, k - 1, l + l1); ++ break; ++ case UP: ++ j2 = BlockPosition.a(j + i2, k + 16 - 1, l + l1); ++ k2 = BlockPosition.a(j + i2, k + 16, l + l1); ++ break; ++ case NORTH: ++ j2 = BlockPosition.a(j + l1, k + i2, l); ++ k2 = BlockPosition.a(j + l1, k + i2, l - 1); ++ break; ++ case SOUTH: ++ j2 = BlockPosition.a(j + l1, k + i2, l + 16 - 1); ++ k2 = BlockPosition.a(j + l1, k + i2, l + 16); ++ break; ++ case WEST: ++ j2 = BlockPosition.a(j, k + l1, l + i2); ++ k2 = BlockPosition.a(j - 1, k + l1, l + i2); ++ break; ++ default: ++ j2 = BlockPosition.a(j + 16 - 1, k + l1, l + i2); ++ k2 = BlockPosition.a(j + 16, k + l1, l + i2); ++ } ++ ++ lightenginelayer.a(j2, k2, lightenginelayer.b(j2, k2, lightenginelayer.c(j2)), false); ++ lightenginelayer.a(k2, j2, lightenginelayer.b(k2, j2, lightenginelayer.c(k2)), false); ++ } ++ } ++ } ++ } ++ ++ } ++ } ++ ++ protected void k(long i) {} ++ ++ protected void l(long i) {} ++ ++ protected void b(long i, boolean flag) {} ++ ++ public void c(long i, boolean flag) { ++ if (flag) { ++ this.o.add(i); ++ } else { ++ this.o.remove(i); ++ } ++ ++ } ++ ++ protected void a(long i, @Nullable NibbleArray nibblearray, boolean flag) { ++ if (nibblearray != null) { ++ this.i.put(i, nibblearray); ++ if (!flag) { ++ this.n.add(i); ++ } ++ } else { ++ this.i.remove(i); ++ } ++ ++ } ++ ++ protected void d(long i, boolean flag) { ++ boolean flag1 = this.b.contains(i); ++ ++ if (!flag1 && !flag) { ++ this.d.add(i); ++ this.a(Long.MAX_VALUE, i, 0, true); ++ } ++ ++ if (flag1 && flag) { ++ this.c.add(i); ++ this.a(Long.MAX_VALUE, i, 2, false); ++ } ++ ++ } ++ ++ protected void d() { ++ if (this.b()) { ++ this.b(Integer.MAX_VALUE); ++ } ++ ++ } ++ ++ protected void e() { ++ if (!this.g.isEmpty()) { ++ M m0 = this.f.b(); ++ ++ m0.d(); ++ this.e = m0; ++ this.g.clear(); ++ } ++ ++ if (!this.h.isEmpty()) { ++ LongIterator longiterator = this.h.iterator(); ++ ++ while (longiterator.hasNext()) { ++ long i = longiterator.nextLong(); ++ ++ this.m.a(this.l, SectionPosition.a(i)); ++ } ++ ++ this.h.clear(); ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java +new file mode 100644 +index 0000000000000000000000000000000000000000..242a2c5dea1241b515b9eee7c334ab3c31ad9d12 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java +@@ -0,0 +1,80 @@ ++package net.minecraft.world.level.lighting; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import javax.annotation.Nullable; ++import net.minecraft.world.level.chunk.NibbleArray; ++ ++public abstract class LightEngineStorageArray> { ++ ++ private final long[] b = new long[2]; ++ private final NibbleArray[] c = new NibbleArray[2]; ++ private boolean d; ++ protected final Long2ObjectOpenHashMap a; ++ ++ protected LightEngineStorageArray(Long2ObjectOpenHashMap long2objectopenhashmap) { ++ this.a = long2objectopenhashmap; ++ this.c(); ++ this.d = true; ++ } ++ ++ public abstract M b(); ++ ++ public void a(long i) { ++ this.a.put(i, ((NibbleArray) this.a.get(i)).b()); ++ this.c(); ++ } ++ ++ public boolean b(long i) { ++ return this.a.containsKey(i); ++ } ++ ++ @Nullable ++ public NibbleArray c(long i) { ++ if (this.d) { ++ for (int j = 0; j < 2; ++j) { ++ if (i == this.b[j]) { ++ return this.c[j]; ++ } ++ } ++ } ++ ++ NibbleArray nibblearray = (NibbleArray) this.a.get(i); ++ ++ if (nibblearray == null) { ++ return null; ++ } else { ++ if (this.d) { ++ for (int k = 1; k > 0; --k) { ++ this.b[k] = this.b[k - 1]; ++ this.c[k] = this.c[k - 1]; ++ } ++ ++ this.b[0] = i; ++ this.c[0] = nibblearray; ++ } ++ ++ return nibblearray; ++ } ++ } ++ ++ @Nullable ++ public NibbleArray d(long i) { ++ return (NibbleArray) this.a.remove(i); ++ } ++ ++ public void a(long i, NibbleArray nibblearray) { ++ this.a.put(i, nibblearray); ++ } ++ ++ public void c() { ++ for (int i = 0; i < 2; ++i) { ++ this.b[i] = Long.MAX_VALUE; ++ this.c[i] = null; ++ } ++ ++ } ++ ++ public void d() { ++ this.d = false; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageBlock.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageBlock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..06c0635cb40114b078dace69bb5f987e24d25b0f +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageBlock.java +@@ -0,0 +1,35 @@ ++package net.minecraft.world.level.lighting; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.chunk.ILightAccess; ++import net.minecraft.world.level.chunk.NibbleArray; ++ ++public class LightEngineStorageBlock extends LightEngineStorage { ++ ++ protected LightEngineStorageBlock(ILightAccess ilightaccess) { ++ super(EnumSkyBlock.BLOCK, ilightaccess, new LightEngineStorageBlock.a(new Long2ObjectOpenHashMap())); ++ } ++ ++ @Override ++ protected int d(long i) { ++ long j = SectionPosition.e(i); ++ NibbleArray nibblearray = this.a(j, false); ++ ++ return nibblearray == null ? 0 : nibblearray.a(SectionPosition.b(BlockPosition.b(i)), SectionPosition.b(BlockPosition.c(i)), SectionPosition.b(BlockPosition.d(i))); ++ } ++ ++ public static final class a extends LightEngineStorageArray { ++ ++ public a(Long2ObjectOpenHashMap long2objectopenhashmap) { ++ super(long2objectopenhashmap); ++ } ++ ++ @Override ++ public LightEngineStorageBlock.a b() { ++ return new LightEngineStorageBlock.a(this.a.clone()); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java +new file mode 100644 +index 0000000000000000000000000000000000000000..52564cce4146f49a906729b3ed9488a7a829ea3f +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java +@@ -0,0 +1,344 @@ ++package net.minecraft.world.level.lighting; ++ ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.longs.LongOpenHashSet; ++import it.unimi.dsi.fastutil.longs.LongSet; ++import java.util.Arrays; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.chunk.ILightAccess; ++import net.minecraft.world.level.chunk.NibbleArray; ++ ++public class LightEngineStorageSky extends LightEngineStorage { ++ ++ private static final EnumDirection[] k = new EnumDirection[]{EnumDirection.NORTH, EnumDirection.SOUTH, EnumDirection.WEST, EnumDirection.EAST}; ++ private final LongSet l = new LongOpenHashSet(); ++ private final LongSet m = new LongOpenHashSet(); ++ private final LongSet n = new LongOpenHashSet(); ++ private final LongSet o = new LongOpenHashSet(); ++ private volatile boolean p; ++ ++ protected LightEngineStorageSky(ILightAccess ilightaccess) { ++ super(EnumSkyBlock.SKY, ilightaccess, new LightEngineStorageSky.a(new Long2ObjectOpenHashMap(), new Long2IntOpenHashMap(), Integer.MAX_VALUE)); ++ } ++ ++ @Override ++ protected int d(long i) { ++ long j = SectionPosition.e(i); ++ int k = SectionPosition.c(j); ++ LightEngineStorageSky.a lightenginestoragesky_a = (LightEngineStorageSky.a) this.e; ++ int l = lightenginestoragesky_a.c.get(SectionPosition.f(j)); ++ ++ if (l != lightenginestoragesky_a.b && k < l) { ++ NibbleArray nibblearray = this.a((LightEngineStorageArray) lightenginestoragesky_a, j); ++ ++ if (nibblearray == null) { ++ for (i = BlockPosition.f(i); nibblearray == null; nibblearray = this.a((LightEngineStorageArray) lightenginestoragesky_a, j)) { ++ j = SectionPosition.a(j, EnumDirection.UP); ++ ++k; ++ if (k >= l) { ++ return 15; ++ } ++ ++ i = BlockPosition.a(i, 0, 16, 0); ++ } ++ } ++ ++ return nibblearray.a(SectionPosition.b(BlockPosition.b(i)), SectionPosition.b(BlockPosition.c(i)), SectionPosition.b(BlockPosition.d(i))); ++ } else { ++ return 15; ++ } ++ } ++ ++ @Override ++ protected void k(long i) { ++ int j = SectionPosition.c(i); ++ ++ if (((LightEngineStorageSky.a) this.f).b > j) { ++ ((LightEngineStorageSky.a) this.f).b = j; ++ ((LightEngineStorageSky.a) this.f).c.defaultReturnValue(((LightEngineStorageSky.a) this.f).b); ++ } ++ ++ long k = SectionPosition.f(i); ++ int l = ((LightEngineStorageSky.a) this.f).c.get(k); ++ ++ if (l < j + 1) { ++ ((LightEngineStorageSky.a) this.f).c.put(k, j + 1); ++ if (this.o.contains(k)) { ++ this.q(i); ++ if (l > ((LightEngineStorageSky.a) this.f).b) { ++ long i1 = SectionPosition.b(SectionPosition.b(i), l - 1, SectionPosition.d(i)); ++ ++ this.p(i1); ++ } ++ ++ this.f(); ++ } ++ } ++ ++ } ++ ++ private void p(long i) { ++ this.n.add(i); ++ this.m.remove(i); ++ } ++ ++ private void q(long i) { ++ this.m.add(i); ++ this.n.remove(i); ++ } ++ ++ private void f() { ++ this.p = !this.m.isEmpty() || !this.n.isEmpty(); ++ } ++ ++ @Override ++ protected void l(long i) { ++ long j = SectionPosition.f(i); ++ boolean flag = this.o.contains(j); ++ ++ if (flag) { ++ this.p(i); ++ } ++ ++ int k = SectionPosition.c(i); ++ ++ if (((LightEngineStorageSky.a) this.f).c.get(j) == k + 1) { ++ long l; ++ ++ for (l = i; !this.g(l) && this.a(k); l = SectionPosition.a(l, EnumDirection.DOWN)) { ++ --k; ++ } ++ ++ if (this.g(l)) { ++ ((LightEngineStorageSky.a) this.f).c.put(j, k + 1); ++ if (flag) { ++ this.q(l); ++ } ++ } else { ++ ((LightEngineStorageSky.a) this.f).c.remove(j); ++ } ++ } ++ ++ if (flag) { ++ this.f(); ++ } ++ ++ } ++ ++ @Override ++ protected void b(long i, boolean flag) { ++ this.d(); ++ if (flag && this.o.add(i)) { ++ int j = ((LightEngineStorageSky.a) this.f).c.get(i); ++ ++ if (j != ((LightEngineStorageSky.a) this.f).b) { ++ long k = SectionPosition.b(SectionPosition.b(i), j - 1, SectionPosition.d(i)); ++ ++ this.q(k); ++ this.f(); ++ } ++ } else if (!flag) { ++ this.o.remove(i); ++ } ++ ++ } ++ ++ @Override ++ protected boolean a() { ++ return super.a() || this.p; ++ } ++ ++ @Override ++ protected NibbleArray j(long i) { ++ NibbleArray nibblearray = (NibbleArray) this.i.get(i); ++ ++ if (nibblearray != null) { ++ return nibblearray; ++ } else { ++ long j = SectionPosition.a(i, EnumDirection.UP); ++ int k = ((LightEngineStorageSky.a) this.f).c.get(SectionPosition.f(i)); ++ ++ if (k != ((LightEngineStorageSky.a) this.f).b && SectionPosition.c(j) < k) { ++ NibbleArray nibblearray1; ++ ++ while ((nibblearray1 = this.a(j, true)) == null) { ++ j = SectionPosition.a(j, EnumDirection.UP); ++ } ++ ++ return new NibbleArray((new NibbleArrayFlat(nibblearray1, 0)).asBytes()); ++ } else { ++ return new NibbleArray(); ++ } ++ } ++ } ++ ++ @Override ++ protected void a(LightEngineLayer lightenginelayer, boolean flag, boolean flag1) { ++ super.a(lightenginelayer, flag, flag1); ++ if (flag) { ++ LongIterator longiterator; ++ long i; ++ int j; ++ int k; ++ ++ if (!this.m.isEmpty()) { ++ longiterator = this.m.iterator(); ++ ++ while (longiterator.hasNext()) { ++ i = (Long) longiterator.next(); ++ j = this.c(i); ++ if (j != 2 && !this.n.contains(i) && this.l.add(i)) { ++ int l; ++ ++ if (j == 1) { ++ this.a(lightenginelayer, i); ++ if (this.g.add(i)) { ++ ((LightEngineStorageSky.a) this.f).a(i); ++ } ++ ++ Arrays.fill(this.a(i, true).asBytes(), (byte) -1); ++ k = SectionPosition.c(SectionPosition.b(i)); ++ l = SectionPosition.c(SectionPosition.c(i)); ++ int i1 = SectionPosition.c(SectionPosition.d(i)); ++ EnumDirection[] aenumdirection = LightEngineStorageSky.k; ++ int j1 = aenumdirection.length; ++ ++ long k1; ++ ++ for (int l1 = 0; l1 < j1; ++l1) { ++ EnumDirection enumdirection = aenumdirection[l1]; ++ ++ k1 = SectionPosition.a(i, enumdirection); ++ if ((this.n.contains(k1) || !this.l.contains(k1) && !this.m.contains(k1)) && this.g(k1)) { ++ for (int i2 = 0; i2 < 16; ++i2) { ++ for (int j2 = 0; j2 < 16; ++j2) { ++ long k2; ++ long l2; ++ ++ switch (enumdirection) { ++ case NORTH: ++ k2 = BlockPosition.a(k + i2, l + j2, i1); ++ l2 = BlockPosition.a(k + i2, l + j2, i1 - 1); ++ break; ++ case SOUTH: ++ k2 = BlockPosition.a(k + i2, l + j2, i1 + 16 - 1); ++ l2 = BlockPosition.a(k + i2, l + j2, i1 + 16); ++ break; ++ case WEST: ++ k2 = BlockPosition.a(k, l + i2, i1 + j2); ++ l2 = BlockPosition.a(k - 1, l + i2, i1 + j2); ++ break; ++ default: ++ k2 = BlockPosition.a(k + 16 - 1, l + i2, i1 + j2); ++ l2 = BlockPosition.a(k + 16, l + i2, i1 + j2); ++ } ++ ++ lightenginelayer.a(k2, l2, lightenginelayer.b(k2, l2, 0), true); ++ } ++ } ++ } ++ } ++ ++ for (int i3 = 0; i3 < 16; ++i3) { ++ for (j1 = 0; j1 < 16; ++j1) { ++ long j3 = BlockPosition.a(SectionPosition.c(SectionPosition.b(i)) + i3, SectionPosition.c(SectionPosition.c(i)), SectionPosition.c(SectionPosition.d(i)) + j1); ++ ++ k1 = BlockPosition.a(SectionPosition.c(SectionPosition.b(i)) + i3, SectionPosition.c(SectionPosition.c(i)) - 1, SectionPosition.c(SectionPosition.d(i)) + j1); ++ lightenginelayer.a(j3, k1, lightenginelayer.b(j3, k1, 0), true); ++ } ++ } ++ } else { ++ for (k = 0; k < 16; ++k) { ++ for (l = 0; l < 16; ++l) { ++ long k3 = BlockPosition.a(SectionPosition.c(SectionPosition.b(i)) + k, SectionPosition.c(SectionPosition.c(i)) + 16 - 1, SectionPosition.c(SectionPosition.d(i)) + l); ++ ++ lightenginelayer.a(Long.MAX_VALUE, k3, 0, true); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ this.m.clear(); ++ if (!this.n.isEmpty()) { ++ longiterator = this.n.iterator(); ++ ++ while (longiterator.hasNext()) { ++ i = (Long) longiterator.next(); ++ if (this.l.remove(i) && this.g(i)) { ++ for (j = 0; j < 16; ++j) { ++ for (k = 0; k < 16; ++k) { ++ long l3 = BlockPosition.a(SectionPosition.c(SectionPosition.b(i)) + j, SectionPosition.c(SectionPosition.c(i)) + 16 - 1, SectionPosition.c(SectionPosition.d(i)) + k); ++ ++ lightenginelayer.a(Long.MAX_VALUE, l3, 15, false); ++ } ++ } ++ } ++ } ++ } ++ ++ this.n.clear(); ++ this.p = false; ++ } ++ } ++ ++ protected boolean a(int i) { ++ return i >= ((LightEngineStorageSky.a) this.f).b; ++ } ++ ++ protected boolean m(long i) { ++ int j = BlockPosition.c(i); ++ ++ if ((j & 15) != 15) { ++ return false; ++ } else { ++ long k = SectionPosition.e(i); ++ long l = SectionPosition.f(k); ++ ++ if (!this.o.contains(l)) { ++ return false; ++ } else { ++ int i1 = ((LightEngineStorageSky.a) this.f).c.get(l); ++ ++ return SectionPosition.c(i1) == j + 16; ++ } ++ } ++ } ++ ++ protected boolean n(long i) { ++ long j = SectionPosition.f(i); ++ int k = ((LightEngineStorageSky.a) this.f).c.get(j); ++ ++ return k == ((LightEngineStorageSky.a) this.f).b || SectionPosition.c(i) >= k; ++ } ++ ++ protected boolean o(long i) { ++ long j = SectionPosition.f(i); ++ ++ return this.o.contains(j); ++ } ++ ++ public static final class a extends LightEngineStorageArray { ++ ++ private int b; ++ private final Long2IntOpenHashMap c; ++ ++ public a(Long2ObjectOpenHashMap long2objectopenhashmap, Long2IntOpenHashMap long2intopenhashmap, int i) { ++ super(long2objectopenhashmap); ++ this.c = long2intopenhashmap; ++ long2intopenhashmap.defaultReturnValue(i); ++ this.b = i; ++ } ++ ++ @Override ++ public LightEngineStorageSky.a b() { ++ return new LightEngineStorageSky.a(this.a.clone(), this.c.clone(), this.b); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/lighting/NibbleArrayFlat.java b/src/main/java/net/minecraft/world/level/lighting/NibbleArrayFlat.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dc059d5f332fdf561c7e410ce9959154de25006a +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/lighting/NibbleArrayFlat.java +@@ -0,0 +1,31 @@ ++package net.minecraft.world.level.lighting; ++ ++import net.minecraft.world.level.chunk.NibbleArray; ++ ++public class NibbleArrayFlat extends NibbleArray { ++ ++ public NibbleArrayFlat() { ++ super(128); ++ } ++ ++ public NibbleArrayFlat(NibbleArray nibblearray, int i) { ++ super(128); ++ System.arraycopy(nibblearray.asBytes(), i * 128, this.a, 0, 128); ++ } ++ ++ @Override ++ protected int b(int i, int j, int k) { ++ return k << 4 | i; ++ } ++ ++ @Override ++ public byte[] asBytes() { ++ byte[] abyte = new byte[2048]; ++ ++ for (int i = 0; i < 16; ++i) { ++ System.arraycopy(this.a, 0, abyte, i * 128, 128); ++ } ++ ++ return abyte; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/material/Fluid.java b/src/main/java/net/minecraft/world/level/material/Fluid.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9a93b204973eef4c02a52f33fd9578a43603ed98 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/material/Fluid.java +@@ -0,0 +1,86 @@ ++package net.minecraft.world.level.material; ++ ++import com.google.common.collect.ImmutableMap; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.MapCodec; ++import java.util.Random; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.core.IRegistry; ++import net.minecraft.tags.Tag; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.block.state.IBlockDataHolder; ++import net.minecraft.world.level.block.state.properties.IBlockState; ++import net.minecraft.world.phys.Vec3D; ++import net.minecraft.world.phys.shapes.VoxelShape; ++ ++public final class Fluid extends IBlockDataHolder { ++ ++ public static final Codec a = a((Codec) IRegistry.FLUID, FluidType::h).stable(); ++ ++ public Fluid(FluidType fluidtype, ImmutableMap, Comparable> immutablemap, MapCodec mapcodec) { ++ super(fluidtype, immutablemap, mapcodec); ++ } ++ ++ public FluidType getType() { ++ return (FluidType) this.c; ++ } ++ ++ public boolean isSource() { ++ return this.getType().c(this); ++ } ++ ++ public boolean isEmpty() { ++ return this.getType().b(); ++ } ++ ++ public float getHeight(IBlockAccess iblockaccess, BlockPosition blockposition) { ++ return this.getType().a(this, iblockaccess, blockposition); ++ } ++ ++ public float d() { ++ return this.getType().a(this); ++ } ++ ++ public int e() { ++ return this.getType().d(this); ++ } ++ ++ public void a(World world, BlockPosition blockposition) { ++ this.getType().a(world, blockposition, this); ++ } ++ ++ public boolean f() { ++ return this.getType().j(); ++ } ++ ++ public void b(World world, BlockPosition blockposition, Random random) { ++ this.getType().b(world, blockposition, this, random); ++ } ++ ++ public Vec3D c(IBlockAccess iblockaccess, BlockPosition blockposition) { ++ return this.getType().a(iblockaccess, blockposition, this); ++ } ++ ++ public IBlockData getBlockData() { ++ return this.getType().b(this); ++ } ++ ++ public boolean a(Tag tag) { ++ return this.getType().a(tag); ++ } ++ ++ public float i() { ++ return this.getType().c(); ++ } ++ ++ public boolean a(IBlockAccess iblockaccess, BlockPosition blockposition, FluidType fluidtype, EnumDirection enumdirection) { ++ return this.getType().a(this, iblockaccess, blockposition, fluidtype, enumdirection); ++ } ++ ++ public VoxelShape d(IBlockAccess iblockaccess, BlockPosition blockposition) { ++ return this.getType().b(this, iblockaccess, blockposition); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathEntity.java b/src/main/java/net/minecraft/world/level/pathfinder/PathEntity.java +new file mode 100644 +index 0000000000000000000000000000000000000000..606027de777750f6d2ab0d7f1ef387ed4f0c6092 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathEntity.java +@@ -0,0 +1,134 @@ ++package net.minecraft.world.level.pathfinder; ++ ++import java.util.List; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.phys.Vec3D; ++ ++public class PathEntity { ++ ++ private final List a; ++ private PathPoint[] b = new PathPoint[0]; ++ private PathPoint[] c = new PathPoint[0]; ++ private int e; ++ private final BlockPosition f; ++ private final float g; ++ private final boolean h; ++ ++ public PathEntity(List list, BlockPosition blockposition, boolean flag) { ++ this.a = list; ++ this.f = blockposition; ++ this.g = list.isEmpty() ? Float.MAX_VALUE : ((PathPoint) this.a.get(this.a.size() - 1)).c(this.f); ++ this.h = flag; ++ } ++ ++ public void a() { ++ ++this.e; ++ } ++ ++ public boolean b() { ++ return this.e <= 0; ++ } ++ ++ public boolean c() { ++ return this.e >= this.a.size(); ++ } ++ ++ @Nullable ++ public PathPoint d() { ++ return !this.a.isEmpty() ? (PathPoint) this.a.get(this.a.size() - 1) : null; ++ } ++ ++ public PathPoint a(int i) { ++ return (PathPoint) this.a.get(i); ++ } ++ ++ public void b(int i) { ++ if (this.a.size() > i) { ++ this.a.subList(i, this.a.size()).clear(); ++ } ++ ++ } ++ ++ public void a(int i, PathPoint pathpoint) { ++ this.a.set(i, pathpoint); ++ } ++ ++ public int e() { ++ return this.a.size(); ++ } ++ ++ public int f() { ++ return this.e; ++ } ++ ++ public void c(int i) { ++ this.e = i; ++ } ++ ++ public Vec3D a(Entity entity, int i) { ++ PathPoint pathpoint = (PathPoint) this.a.get(i); ++ double d0 = (double) pathpoint.a + (double) ((int) (entity.getWidth() + 1.0F)) * 0.5D; ++ double d1 = (double) pathpoint.b; ++ double d2 = (double) pathpoint.c + (double) ((int) (entity.getWidth() + 1.0F)) * 0.5D; ++ ++ return new Vec3D(d0, d1, d2); ++ } ++ ++ public BlockPosition d(int i) { ++ return ((PathPoint) this.a.get(i)).a(); ++ } ++ ++ public Vec3D a(Entity entity) { ++ return this.a(entity, this.e); ++ } ++ ++ public BlockPosition g() { ++ return ((PathPoint) this.a.get(this.e)).a(); ++ } ++ ++ public PathPoint h() { ++ return (PathPoint) this.a.get(this.e); ++ } ++ ++ @Nullable ++ public PathPoint i() { ++ return this.e > 0 ? (PathPoint) this.a.get(this.e - 1) : null; ++ } ++ ++ public boolean a(@Nullable PathEntity pathentity) { ++ if (pathentity == null) { ++ return false; ++ } else if (pathentity.a.size() != this.a.size()) { ++ return false; ++ } else { ++ for (int i = 0; i < this.a.size(); ++i) { ++ PathPoint pathpoint = (PathPoint) this.a.get(i); ++ PathPoint pathpoint1 = (PathPoint) pathentity.a.get(i); ++ ++ if (pathpoint.a != pathpoint1.a || pathpoint.b != pathpoint1.b || pathpoint.c != pathpoint1.c) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ } ++ ++ public boolean j() { ++ return this.h; ++ } ++ ++ public String toString() { ++ return "Path(length=" + this.a.size() + ")"; ++ } ++ ++ public BlockPosition m() { ++ return this.f; ++ } ++ ++ public float n() { ++ return this.g; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathPoint.java b/src/main/java/net/minecraft/world/level/pathfinder/PathPoint.java +new file mode 100644 +index 0000000000000000000000000000000000000000..43cc9430972a18cbf03a590d576ed200e3836017 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathPoint.java +@@ -0,0 +1,106 @@ ++package net.minecraft.world.level.pathfinder; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.util.MathHelper; ++ ++public class PathPoint { ++ ++ public final int a; ++ public final int b; ++ public final int c; ++ private final int m; ++ public int d = -1; ++ public float e; ++ public float f; ++ public float g; ++ public PathPoint h; ++ public boolean i; ++ public float j; ++ public float k; ++ public PathType l; ++ ++ public PathPoint(int i, int j, int k) { ++ this.l = PathType.BLOCKED; ++ this.a = i; ++ this.b = j; ++ this.c = k; ++ this.m = b(i, j, k); ++ } ++ ++ public PathPoint a(int i, int j, int k) { ++ PathPoint pathpoint = new PathPoint(i, j, k); ++ ++ pathpoint.d = this.d; ++ pathpoint.e = this.e; ++ pathpoint.f = this.f; ++ pathpoint.g = this.g; ++ pathpoint.h = this.h; ++ pathpoint.i = this.i; ++ pathpoint.j = this.j; ++ pathpoint.k = this.k; ++ pathpoint.l = this.l; ++ return pathpoint; ++ } ++ ++ public static int b(int i, int j, int k) { ++ return j & 255 | (i & 32767) << 8 | (k & 32767) << 24 | (i < 0 ? Integer.MIN_VALUE : 0) | (k < 0 ? '\u8000' : 0); ++ } ++ ++ public float a(PathPoint pathpoint) { ++ float f = (float) (pathpoint.a - this.a); ++ float f1 = (float) (pathpoint.b - this.b); ++ float f2 = (float) (pathpoint.c - this.c); ++ ++ return MathHelper.c(f * f + f1 * f1 + f2 * f2); ++ } ++ ++ public float b(PathPoint pathpoint) { ++ float f = (float) (pathpoint.a - this.a); ++ float f1 = (float) (pathpoint.b - this.b); ++ float f2 = (float) (pathpoint.c - this.c); ++ ++ return f * f + f1 * f1 + f2 * f2; ++ } ++ ++ public float c(PathPoint pathpoint) { ++ float f = (float) Math.abs(pathpoint.a - this.a); ++ float f1 = (float) Math.abs(pathpoint.b - this.b); ++ float f2 = (float) Math.abs(pathpoint.c - this.c); ++ ++ return f + f1 + f2; ++ } ++ ++ public float c(BlockPosition blockposition) { ++ float f = (float) Math.abs(blockposition.getX() - this.a); ++ float f1 = (float) Math.abs(blockposition.getY() - this.b); ++ float f2 = (float) Math.abs(blockposition.getZ() - this.c); ++ ++ return f + f1 + f2; ++ } ++ ++ public BlockPosition a() { ++ return new BlockPosition(this.a, this.b, this.c); ++ } ++ ++ public boolean equals(Object object) { ++ if (!(object instanceof PathPoint)) { ++ return false; ++ } else { ++ PathPoint pathpoint = (PathPoint) object; ++ ++ return this.m == pathpoint.m && this.a == pathpoint.a && this.b == pathpoint.b && this.c == pathpoint.c; ++ } ++ } ++ ++ public int hashCode() { ++ return this.m; ++ } ++ ++ public boolean c() { ++ return this.d >= 0; ++ } ++ ++ public String toString() { ++ return "Node{x=" + this.a + ", y=" + this.b + ", z=" + this.c + '}'; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathType.java b/src/main/java/net/minecraft/world/level/pathfinder/PathType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fd20802155097d4951cbe273f64de4809dee5c96 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathType.java +@@ -0,0 +1,16 @@ ++package net.minecraft.world.level.pathfinder; ++ ++public enum PathType { ++ ++ BLOCKED(-1.0F), OPEN(0.0F), WALKABLE(0.0F), WALKABLE_DOOR(0.0F), TRAPDOOR(0.0F), FENCE(-1.0F), LAVA(-1.0F), WATER(8.0F), WATER_BORDER(8.0F), RAIL(0.0F), UNPASSABLE_RAIL(-1.0F), DANGER_FIRE(8.0F), DAMAGE_FIRE(16.0F), DANGER_CACTUS(8.0F), DAMAGE_CACTUS(-1.0F), DANGER_OTHER(8.0F), DAMAGE_OTHER(-1.0F), DOOR_OPEN(0.0F), DOOR_WOOD_CLOSED(-1.0F), DOOR_IRON_CLOSED(-1.0F), BREACH(4.0F), LEAVES(-1.0F), STICKY_HONEY(8.0F), COCOA(0.0F); ++ ++ private final float y; ++ ++ private PathType(float f) { ++ this.y = f; ++ } ++ ++ public float a() { ++ return this.y; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java b/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..39cd22a820fdc4c75aefb625b45b0c8c6ce1f199 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java +@@ -0,0 +1,151 @@ ++package net.minecraft.world.level.pathfinder; ++ ++import com.google.common.collect.ImmutableSet; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Sets; ++import java.util.Comparator; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Optional; ++import java.util.Set; ++import java.util.function.Function; ++import java.util.stream.Collectors; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.level.ChunkCache; ++ ++public class Pathfinder { ++ ++ private final PathPoint[] a = new PathPoint[32]; ++ private final int b; ++ private final PathfinderAbstract c; ++ private final Path d = new Path(); ++ ++ public Pathfinder(PathfinderAbstract pathfinderabstract, int i) { ++ this.c = pathfinderabstract; ++ this.b = i; ++ } ++ ++ @Nullable ++ public PathEntity a(ChunkCache chunkcache, EntityInsentient entityinsentient, Set set, float f, int i, float f1) { ++ this.d.a(); ++ this.c.a(chunkcache, entityinsentient); ++ PathPoint pathpoint = this.c.b(); ++ Map map = (Map) set.stream().collect(Collectors.toMap((blockposition) -> { ++ return this.c.a((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()); ++ }, Function.identity())); ++ PathEntity pathentity = this.a(pathpoint, map, f, i, f1); ++ ++ this.c.a(); ++ return pathentity; ++ } ++ ++ @Nullable ++ private PathEntity a(PathPoint pathpoint, Map map, float f, int i, float f1) { ++ Set set = map.keySet(); ++ ++ pathpoint.e = 0.0F; ++ pathpoint.f = this.a(pathpoint, set); ++ pathpoint.g = pathpoint.f; ++ this.d.a(); ++ this.d.a(pathpoint); ++ Set set1 = ImmutableSet.of(); ++ int j = 0; ++ Set set2 = Sets.newHashSetWithExpectedSize(set.size()); ++ int k = (int) ((float) this.b * f1); ++ ++ while (!this.d.e()) { ++ ++j; ++ if (j >= k) { ++ break; ++ } ++ ++ PathPoint pathpoint1 = this.d.c(); ++ ++ pathpoint1.i = true; ++ Iterator iterator = set.iterator(); ++ ++ while (iterator.hasNext()) { ++ PathDestination pathdestination = (PathDestination) iterator.next(); ++ ++ if (pathpoint1.c((PathPoint) pathdestination) <= (float) i) { ++ pathdestination.e(); ++ set2.add(pathdestination); ++ } ++ } ++ ++ if (!set2.isEmpty()) { ++ break; ++ } ++ ++ if (pathpoint1.a(pathpoint) < f) { ++ int l = this.c.a(this.a, pathpoint1); ++ ++ for (int i1 = 0; i1 < l; ++i1) { ++ PathPoint pathpoint2 = this.a[i1]; ++ float f2 = pathpoint1.a(pathpoint2); ++ ++ pathpoint2.j = pathpoint1.j + f2; ++ float f3 = pathpoint1.e + f2 + pathpoint2.k; ++ ++ if (pathpoint2.j < f && (!pathpoint2.c() || f3 < pathpoint2.e)) { ++ pathpoint2.h = pathpoint1; ++ pathpoint2.e = f3; ++ pathpoint2.f = this.a(pathpoint2, set) * 1.5F; ++ if (pathpoint2.c()) { ++ this.d.a(pathpoint2, pathpoint2.e + pathpoint2.f); ++ } else { ++ pathpoint2.g = pathpoint2.e + pathpoint2.f; ++ this.d.a(pathpoint2); ++ } ++ } ++ } ++ } ++ } ++ ++ Optional optional = !set2.isEmpty() ? set2.stream().map((pathdestination1) -> { ++ return this.a(pathdestination1.d(), (BlockPosition) map.get(pathdestination1), true); ++ }).min(Comparator.comparingInt(PathEntity::e)) : set.stream().map((pathdestination1) -> { ++ return this.a(pathdestination1.d(), (BlockPosition) map.get(pathdestination1), false); ++ }).min(Comparator.comparingDouble(PathEntity::n).thenComparingInt(PathEntity::e)); ++ ++ if (!optional.isPresent()) { ++ return null; ++ } else { ++ PathEntity pathentity = (PathEntity) optional.get(); ++ ++ return pathentity; ++ } ++ } ++ ++ private float a(PathPoint pathpoint, Set set) { ++ float f = Float.MAX_VALUE; ++ ++ float f1; ++ ++ for (Iterator iterator = set.iterator(); iterator.hasNext(); f = Math.min(f1, f)) { ++ PathDestination pathdestination = (PathDestination) iterator.next(); ++ ++ f1 = pathpoint.a(pathdestination); ++ pathdestination.a(f1, pathpoint); ++ } ++ ++ return f; ++ } ++ ++ private PathEntity a(PathPoint pathpoint, BlockPosition blockposition, boolean flag) { ++ List list = Lists.newArrayList(); ++ PathPoint pathpoint1 = pathpoint; ++ ++ list.add(0, pathpoint); ++ ++ while (pathpoint1.h != null) { ++ pathpoint1 = pathpoint1.h; ++ list.add(0, pathpoint1); ++ } ++ ++ return new PathEntity(list, blockposition, flag); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathfinderAbstract.java b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderAbstract.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f2080bd50db04af6eabec4b4b757d6dadfb1a2f5 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderAbstract.java +@@ -0,0 +1,82 @@ ++package net.minecraft.world.level.pathfinder; ++ ++import it.unimi.dsi.fastutil.ints.Int2ObjectMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.level.ChunkCache; ++import net.minecraft.world.level.IBlockAccess; ++ ++public abstract class PathfinderAbstract { ++ ++ protected ChunkCache a; ++ protected EntityInsentient b; ++ protected final Int2ObjectMap c = new Int2ObjectOpenHashMap(); ++ protected int d; ++ protected int e; ++ protected int f; ++ protected boolean g; ++ protected boolean h; ++ protected boolean i; ++ ++ public PathfinderAbstract() {} ++ ++ public void a(ChunkCache chunkcache, EntityInsentient entityinsentient) { ++ this.a = chunkcache; ++ this.b = entityinsentient; ++ this.c.clear(); ++ this.d = MathHelper.d(entityinsentient.getWidth() + 1.0F); ++ this.e = MathHelper.d(entityinsentient.getHeight() + 1.0F); ++ this.f = MathHelper.d(entityinsentient.getWidth() + 1.0F); ++ } ++ ++ public void a() { ++ this.a = null; ++ this.b = null; ++ } ++ ++ protected PathPoint a(BlockPosition blockposition) { ++ return this.a(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ } ++ ++ protected PathPoint a(int i, int j, int k) { ++ return (PathPoint) this.c.computeIfAbsent(PathPoint.b(i, j, k), (l) -> { ++ return new PathPoint(i, j, k); ++ }); ++ } ++ ++ public abstract PathPoint b(); ++ ++ public abstract PathDestination a(double d0, double d1, double d2); ++ ++ public abstract int a(PathPoint[] apathpoint, PathPoint pathpoint); ++ ++ public abstract PathType a(IBlockAccess iblockaccess, int i, int j, int k, EntityInsentient entityinsentient, int l, int i1, int j1, boolean flag, boolean flag1); ++ ++ public abstract PathType a(IBlockAccess iblockaccess, int i, int j, int k); ++ ++ public void a(boolean flag) { ++ this.g = flag; ++ } ++ ++ public void b(boolean flag) { ++ this.h = flag; ++ } ++ ++ public void c(boolean flag) { ++ this.i = flag; ++ } ++ ++ public boolean c() { ++ return this.g; ++ } ++ ++ public boolean d() { ++ return this.h; ++ } ++ ++ public boolean e() { ++ return this.i; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ed9c1dfbc84b9573784e6531186b3cd9513ddf75 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java +@@ -0,0 +1,536 @@ ++package net.minecraft.world.level.pathfinder; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2BooleanMap; ++import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; ++import java.util.EnumSet; ++import java.util.Iterator; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.tags.Tag; ++import net.minecraft.tags.TagsBlock; ++import net.minecraft.tags.TagsFluid; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.level.ChunkCache; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.BlockCampfire; ++import net.minecraft.world.level.block.BlockDoor; ++import net.minecraft.world.level.block.BlockFenceGate; ++import net.minecraft.world.level.block.BlockLeaves; ++import net.minecraft.world.level.block.BlockMinecartTrackAbstract; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.level.material.FluidTypes; ++import net.minecraft.world.level.material.Material; ++import net.minecraft.world.phys.AxisAlignedBB; ++import net.minecraft.world.phys.Vec3D; ++import net.minecraft.world.phys.shapes.VoxelShape; ++ ++public class PathfinderNormal extends PathfinderAbstract { ++ ++ protected float j; ++ private final Long2ObjectMap k = new Long2ObjectOpenHashMap(); ++ private final Object2BooleanMap l = new Object2BooleanOpenHashMap(); ++ ++ public PathfinderNormal() {} ++ ++ @Override ++ public void a(ChunkCache chunkcache, EntityInsentient entityinsentient) { ++ super.a(chunkcache, entityinsentient); ++ this.j = entityinsentient.a(PathType.WATER); ++ } ++ ++ @Override ++ public void a() { ++ this.b.a(PathType.WATER, this.j); ++ this.k.clear(); ++ this.l.clear(); ++ super.a(); ++ } ++ ++ @Override ++ public PathPoint b() { ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ int i = MathHelper.floor(this.b.locY()); ++ IBlockData iblockdata = this.a.getType(blockposition_mutableblockposition.c(this.b.locX(), (double) i, this.b.locZ())); ++ BlockPosition blockposition; ++ ++ if (this.b.a(iblockdata.getFluid().getType())) { ++ while (this.b.a(iblockdata.getFluid().getType())) { ++ ++i; ++ iblockdata = this.a.getType(blockposition_mutableblockposition.c(this.b.locX(), (double) i, this.b.locZ())); ++ } ++ ++ --i; ++ } else if (this.e() && this.b.isInWater()) { ++ while (iblockdata.getBlock() == Blocks.WATER || iblockdata.getFluid() == FluidTypes.WATER.a(false)) { ++ ++i; ++ iblockdata = this.a.getType(blockposition_mutableblockposition.c(this.b.locX(), (double) i, this.b.locZ())); ++ } ++ ++ --i; ++ } else if (this.b.isOnGround()) { ++ i = MathHelper.floor(this.b.locY() + 0.5D); ++ } else { ++ for (blockposition = this.b.getChunkCoordinates(); (this.a.getType(blockposition).isAir() || this.a.getType(blockposition).a((IBlockAccess) this.a, blockposition, PathMode.LAND)) && blockposition.getY() > 0; blockposition = blockposition.down()) { ++ ; ++ } ++ ++ i = blockposition.up().getY(); ++ } ++ ++ blockposition = this.b.getChunkCoordinates(); ++ PathType pathtype = this.a(this.b, blockposition.getX(), i, blockposition.getZ()); ++ ++ if (this.b.a(pathtype) < 0.0F) { ++ AxisAlignedBB axisalignedbb = this.b.getBoundingBox(); ++ ++ if (this.b(blockposition_mutableblockposition.c(axisalignedbb.minX, (double) i, axisalignedbb.minZ)) || this.b(blockposition_mutableblockposition.c(axisalignedbb.minX, (double) i, axisalignedbb.maxZ)) || this.b(blockposition_mutableblockposition.c(axisalignedbb.maxX, (double) i, axisalignedbb.minZ)) || this.b(blockposition_mutableblockposition.c(axisalignedbb.maxX, (double) i, axisalignedbb.maxZ))) { ++ PathPoint pathpoint = this.a((BlockPosition) blockposition_mutableblockposition); ++ ++ pathpoint.l = this.a(this.b, pathpoint.a()); ++ pathpoint.k = this.b.a(pathpoint.l); ++ return pathpoint; ++ } ++ } ++ ++ PathPoint pathpoint1 = this.a(blockposition.getX(), i, blockposition.getZ()); ++ ++ pathpoint1.l = this.a(this.b, pathpoint1.a()); ++ pathpoint1.k = this.b.a(pathpoint1.l); ++ return pathpoint1; ++ } ++ ++ private boolean b(BlockPosition blockposition) { ++ PathType pathtype = this.a(this.b, blockposition); ++ ++ return this.b.a(pathtype) >= 0.0F; ++ } ++ ++ @Override ++ public PathDestination a(double d0, double d1, double d2) { ++ return new PathDestination(this.a(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2))); ++ } ++ ++ @Override ++ public int a(PathPoint[] apathpoint, PathPoint pathpoint) { ++ int i = 0; ++ int j = 0; ++ PathType pathtype = this.a(this.b, pathpoint.a, pathpoint.b + 1, pathpoint.c); ++ PathType pathtype1 = this.a(this.b, pathpoint.a, pathpoint.b, pathpoint.c); ++ ++ if (this.b.a(pathtype) >= 0.0F && pathtype1 != PathType.STICKY_HONEY) { ++ j = MathHelper.d(Math.max(1.0F, this.b.G)); ++ } ++ ++ double d0 = a((IBlockAccess) this.a, new BlockPosition(pathpoint.a, pathpoint.b, pathpoint.c)); ++ PathPoint pathpoint1 = this.a(pathpoint.a, pathpoint.b, pathpoint.c + 1, j, d0, EnumDirection.SOUTH, pathtype1); ++ ++ if (this.a(pathpoint1, pathpoint)) { ++ apathpoint[i++] = pathpoint1; ++ } ++ ++ PathPoint pathpoint2 = this.a(pathpoint.a - 1, pathpoint.b, pathpoint.c, j, d0, EnumDirection.WEST, pathtype1); ++ ++ if (this.a(pathpoint2, pathpoint)) { ++ apathpoint[i++] = pathpoint2; ++ } ++ ++ PathPoint pathpoint3 = this.a(pathpoint.a + 1, pathpoint.b, pathpoint.c, j, d0, EnumDirection.EAST, pathtype1); ++ ++ if (this.a(pathpoint3, pathpoint)) { ++ apathpoint[i++] = pathpoint3; ++ } ++ ++ PathPoint pathpoint4 = this.a(pathpoint.a, pathpoint.b, pathpoint.c - 1, j, d0, EnumDirection.NORTH, pathtype1); ++ ++ if (this.a(pathpoint4, pathpoint)) { ++ apathpoint[i++] = pathpoint4; ++ } ++ ++ PathPoint pathpoint5 = this.a(pathpoint.a - 1, pathpoint.b, pathpoint.c - 1, j, d0, EnumDirection.NORTH, pathtype1); ++ ++ if (this.a(pathpoint, pathpoint2, pathpoint4, pathpoint5)) { ++ apathpoint[i++] = pathpoint5; ++ } ++ ++ PathPoint pathpoint6 = this.a(pathpoint.a + 1, pathpoint.b, pathpoint.c - 1, j, d0, EnumDirection.NORTH, pathtype1); ++ ++ if (this.a(pathpoint, pathpoint3, pathpoint4, pathpoint6)) { ++ apathpoint[i++] = pathpoint6; ++ } ++ ++ PathPoint pathpoint7 = this.a(pathpoint.a - 1, pathpoint.b, pathpoint.c + 1, j, d0, EnumDirection.SOUTH, pathtype1); ++ ++ if (this.a(pathpoint, pathpoint2, pathpoint1, pathpoint7)) { ++ apathpoint[i++] = pathpoint7; ++ } ++ ++ PathPoint pathpoint8 = this.a(pathpoint.a + 1, pathpoint.b, pathpoint.c + 1, j, d0, EnumDirection.SOUTH, pathtype1); ++ ++ if (this.a(pathpoint, pathpoint3, pathpoint1, pathpoint8)) { ++ apathpoint[i++] = pathpoint8; ++ } ++ ++ return i; ++ } ++ ++ private boolean a(PathPoint pathpoint, PathPoint pathpoint1) { ++ return pathpoint != null && !pathpoint.i && (pathpoint.k >= 0.0F || pathpoint1.k < 0.0F); ++ } ++ ++ private boolean a(PathPoint pathpoint, @Nullable PathPoint pathpoint1, @Nullable PathPoint pathpoint2, @Nullable PathPoint pathpoint3) { ++ if (pathpoint3 != null && pathpoint2 != null && pathpoint1 != null) { ++ if (pathpoint3.i) { ++ return false; ++ } else if (pathpoint2.b <= pathpoint.b && pathpoint1.b <= pathpoint.b) { ++ if (pathpoint1.l != PathType.WALKABLE_DOOR && pathpoint2.l != PathType.WALKABLE_DOOR && pathpoint3.l != PathType.WALKABLE_DOOR) { ++ boolean flag = pathpoint2.l == PathType.FENCE && pathpoint1.l == PathType.FENCE && (double) this.b.getWidth() < 0.5D; ++ ++ return pathpoint3.k >= 0.0F && (pathpoint2.b < pathpoint.b || pathpoint2.k >= 0.0F || flag) && (pathpoint1.b < pathpoint.b || pathpoint1.k >= 0.0F || flag); ++ } else { ++ return false; ++ } ++ } else { ++ return false; ++ } ++ } else { ++ return false; ++ } ++ } ++ ++ private boolean a(PathPoint pathpoint) { ++ Vec3D vec3d = new Vec3D((double) pathpoint.a - this.b.locX(), (double) pathpoint.b - this.b.locY(), (double) pathpoint.c - this.b.locZ()); ++ AxisAlignedBB axisalignedbb = this.b.getBoundingBox(); ++ int i = MathHelper.f(vec3d.f() / axisalignedbb.a()); ++ ++ vec3d = vec3d.a((double) (1.0F / (float) i)); ++ ++ for (int j = 1; j <= i; ++j) { ++ axisalignedbb = axisalignedbb.c(vec3d); ++ if (this.a(axisalignedbb)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ public static double a(IBlockAccess iblockaccess, BlockPosition blockposition) { ++ BlockPosition blockposition1 = blockposition.down(); ++ VoxelShape voxelshape = iblockaccess.getType(blockposition1).getCollisionShape(iblockaccess, blockposition1); ++ ++ return (double) blockposition1.getY() + (voxelshape.isEmpty() ? 0.0D : voxelshape.c(EnumDirection.EnumAxis.Y)); ++ } ++ ++ @Nullable ++ private PathPoint a(int i, int j, int k, int l, double d0, EnumDirection enumdirection, PathType pathtype) { ++ PathPoint pathpoint = null; ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ double d1 = a((IBlockAccess) this.a, (BlockPosition) blockposition_mutableblockposition.d(i, j, k)); ++ ++ if (d1 - d0 > 1.125D) { ++ return null; ++ } else { ++ PathType pathtype1 = this.a(this.b, i, j, k); ++ float f = this.b.a(pathtype1); ++ double d2 = (double) this.b.getWidth() / 2.0D; ++ ++ if (f >= 0.0F) { ++ pathpoint = this.a(i, j, k); ++ pathpoint.l = pathtype1; ++ pathpoint.k = Math.max(pathpoint.k, f); ++ } ++ ++ if (pathtype == PathType.FENCE && pathpoint != null && pathpoint.k >= 0.0F && !this.a(pathpoint)) { ++ pathpoint = null; ++ } ++ ++ if (pathtype1 == PathType.WALKABLE) { ++ return pathpoint; ++ } else { ++ if ((pathpoint == null || pathpoint.k < 0.0F) && l > 0 && pathtype1 != PathType.FENCE && pathtype1 != PathType.UNPASSABLE_RAIL && pathtype1 != PathType.TRAPDOOR) { ++ pathpoint = this.a(i, j + 1, k, l - 1, d0, enumdirection, pathtype); ++ if (pathpoint != null && (pathpoint.l == PathType.OPEN || pathpoint.l == PathType.WALKABLE) && this.b.getWidth() < 1.0F) { ++ double d3 = (double) (i - enumdirection.getAdjacentX()) + 0.5D; ++ double d4 = (double) (k - enumdirection.getAdjacentZ()) + 0.5D; ++ AxisAlignedBB axisalignedbb = new AxisAlignedBB(d3 - d2, a((IBlockAccess) this.a, (BlockPosition) blockposition_mutableblockposition.c(d3, (double) (j + 1), d4)) + 0.001D, d4 - d2, d3 + d2, (double) this.b.getHeight() + a((IBlockAccess) this.a, (BlockPosition) blockposition_mutableblockposition.c((double) pathpoint.a, (double) pathpoint.b, (double) pathpoint.c)) - 0.002D, d4 + d2); ++ ++ if (this.a(axisalignedbb)) { ++ pathpoint = null; ++ } ++ } ++ } ++ ++ if (pathtype1 == PathType.WATER && !this.e()) { ++ if (this.a(this.b, i, j - 1, k) != PathType.WATER) { ++ return pathpoint; ++ } ++ ++ while (j > 0) { ++ --j; ++ pathtype1 = this.a(this.b, i, j, k); ++ if (pathtype1 != PathType.WATER) { ++ return pathpoint; ++ } ++ ++ pathpoint = this.a(i, j, k); ++ pathpoint.l = pathtype1; ++ pathpoint.k = Math.max(pathpoint.k, this.b.a(pathtype1)); ++ } ++ } ++ ++ if (pathtype1 == PathType.OPEN) { ++ int i1 = 0; ++ int j1 = j; ++ ++ while (pathtype1 == PathType.OPEN) { ++ --j; ++ PathPoint pathpoint1; ++ ++ if (j < 0) { ++ pathpoint1 = this.a(i, j1, k); ++ pathpoint1.l = PathType.BLOCKED; ++ pathpoint1.k = -1.0F; ++ return pathpoint1; ++ } ++ ++ if (i1++ >= this.b.bP()) { ++ pathpoint1 = this.a(i, j, k); ++ pathpoint1.l = PathType.BLOCKED; ++ pathpoint1.k = -1.0F; ++ return pathpoint1; ++ } ++ ++ pathtype1 = this.a(this.b, i, j, k); ++ f = this.b.a(pathtype1); ++ if (pathtype1 != PathType.OPEN && f >= 0.0F) { ++ pathpoint = this.a(i, j, k); ++ pathpoint.l = pathtype1; ++ pathpoint.k = Math.max(pathpoint.k, f); ++ break; ++ } ++ ++ if (f < 0.0F) { ++ pathpoint1 = this.a(i, j, k); ++ pathpoint1.l = PathType.BLOCKED; ++ pathpoint1.k = -1.0F; ++ return pathpoint1; ++ } ++ } ++ } ++ ++ if (pathtype1 == PathType.FENCE) { ++ pathpoint = this.a(i, j, k); ++ pathpoint.i = true; ++ pathpoint.l = pathtype1; ++ pathpoint.k = pathtype1.a(); ++ } ++ ++ return pathpoint; ++ } ++ } ++ } ++ ++ private boolean a(AxisAlignedBB axisalignedbb) { ++ return (Boolean) this.l.computeIfAbsent(axisalignedbb, (axisalignedbb1) -> { ++ return !this.a.getCubes(this.b, axisalignedbb); ++ }); ++ } ++ ++ @Override ++ public PathType a(IBlockAccess iblockaccess, int i, int j, int k, EntityInsentient entityinsentient, int l, int i1, int j1, boolean flag, boolean flag1) { ++ EnumSet enumset = EnumSet.noneOf(PathType.class); ++ PathType pathtype = PathType.BLOCKED; ++ BlockPosition blockposition = entityinsentient.getChunkCoordinates(); ++ ++ pathtype = this.a(iblockaccess, i, j, k, l, i1, j1, flag, flag1, enumset, pathtype, blockposition); ++ if (enumset.contains(PathType.FENCE)) { ++ return PathType.FENCE; ++ } else if (enumset.contains(PathType.UNPASSABLE_RAIL)) { ++ return PathType.UNPASSABLE_RAIL; ++ } else { ++ PathType pathtype1 = PathType.BLOCKED; ++ Iterator iterator = enumset.iterator(); ++ ++ while (iterator.hasNext()) { ++ PathType pathtype2 = (PathType) iterator.next(); ++ ++ if (entityinsentient.a(pathtype2) < 0.0F) { ++ return pathtype2; ++ } ++ ++ if (entityinsentient.a(pathtype2) >= entityinsentient.a(pathtype1)) { ++ pathtype1 = pathtype2; ++ } ++ } ++ ++ if (pathtype == PathType.OPEN && entityinsentient.a(pathtype1) == 0.0F && l <= 1) { ++ return PathType.OPEN; ++ } else { ++ return pathtype1; ++ } ++ } ++ } ++ ++ public PathType a(IBlockAccess iblockaccess, int i, int j, int k, int l, int i1, int j1, boolean flag, boolean flag1, EnumSet enumset, PathType pathtype, BlockPosition blockposition) { ++ for (int k1 = 0; k1 < l; ++k1) { ++ for (int l1 = 0; l1 < i1; ++l1) { ++ for (int i2 = 0; i2 < j1; ++i2) { ++ int j2 = k1 + i; ++ int k2 = l1 + j; ++ int l2 = i2 + k; ++ PathType pathtype1 = this.a(iblockaccess, j2, k2, l2); ++ ++ pathtype1 = this.a(iblockaccess, flag, flag1, blockposition, pathtype1); ++ if (k1 == 0 && l1 == 0 && i2 == 0) { ++ pathtype = pathtype1; ++ } ++ ++ enumset.add(pathtype1); ++ } ++ } ++ } ++ ++ return pathtype; ++ } ++ ++ protected PathType a(IBlockAccess iblockaccess, boolean flag, boolean flag1, BlockPosition blockposition, PathType pathtype) { ++ if (pathtype == PathType.DOOR_WOOD_CLOSED && flag && flag1) { ++ pathtype = PathType.WALKABLE_DOOR; ++ } ++ ++ if (pathtype == PathType.DOOR_OPEN && !flag1) { ++ pathtype = PathType.BLOCKED; ++ } ++ ++ if (pathtype == PathType.RAIL && !(iblockaccess.getType(blockposition).getBlock() instanceof BlockMinecartTrackAbstract) && !(iblockaccess.getType(blockposition.down()).getBlock() instanceof BlockMinecartTrackAbstract)) { ++ pathtype = PathType.UNPASSABLE_RAIL; ++ } ++ ++ if (pathtype == PathType.LEAVES) { ++ pathtype = PathType.BLOCKED; ++ } ++ ++ return pathtype; ++ } ++ ++ private PathType a(EntityInsentient entityinsentient, BlockPosition blockposition) { ++ return this.a(entityinsentient, blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ } ++ ++ private PathType a(EntityInsentient entityinsentient, int i, int j, int k) { ++ return (PathType) this.k.computeIfAbsent(BlockPosition.a(i, j, k), (l) -> { ++ return this.a(this.a, i, j, k, entityinsentient, this.d, this.e, this.f, this.d(), this.c()); ++ }); ++ } ++ ++ @Override ++ public PathType a(IBlockAccess iblockaccess, int i, int j, int k) { ++ return a(iblockaccess, new BlockPosition.MutableBlockPosition(i, j, k)); ++ } ++ ++ public static PathType a(IBlockAccess iblockaccess, BlockPosition.MutableBlockPosition blockposition_mutableblockposition) { ++ int i = blockposition_mutableblockposition.getX(); ++ int j = blockposition_mutableblockposition.getY(); ++ int k = blockposition_mutableblockposition.getZ(); ++ PathType pathtype = b(iblockaccess, blockposition_mutableblockposition); ++ ++ if (pathtype == PathType.OPEN && j >= 1) { ++ PathType pathtype1 = b(iblockaccess, blockposition_mutableblockposition.d(i, j - 1, k)); ++ ++ pathtype = pathtype1 != PathType.WALKABLE && pathtype1 != PathType.OPEN && pathtype1 != PathType.WATER && pathtype1 != PathType.LAVA ? PathType.WALKABLE : PathType.OPEN; ++ if (pathtype1 == PathType.DAMAGE_FIRE) { ++ pathtype = PathType.DAMAGE_FIRE; ++ } ++ ++ if (pathtype1 == PathType.DAMAGE_CACTUS) { ++ pathtype = PathType.DAMAGE_CACTUS; ++ } ++ ++ if (pathtype1 == PathType.DAMAGE_OTHER) { ++ pathtype = PathType.DAMAGE_OTHER; ++ } ++ ++ if (pathtype1 == PathType.STICKY_HONEY) { ++ pathtype = PathType.STICKY_HONEY; ++ } ++ } ++ ++ if (pathtype == PathType.WALKABLE) { ++ pathtype = a(iblockaccess, blockposition_mutableblockposition.d(i, j, k), pathtype); ++ } ++ ++ return pathtype; ++ } ++ ++ public static PathType a(IBlockAccess iblockaccess, BlockPosition.MutableBlockPosition blockposition_mutableblockposition, PathType pathtype) { ++ int i = blockposition_mutableblockposition.getX(); ++ int j = blockposition_mutableblockposition.getY(); ++ int k = blockposition_mutableblockposition.getZ(); ++ ++ for (int l = -1; l <= 1; ++l) { ++ for (int i1 = -1; i1 <= 1; ++i1) { ++ for (int j1 = -1; j1 <= 1; ++j1) { ++ if (l != 0 || j1 != 0) { ++ blockposition_mutableblockposition.d(i + l, j + i1, k + j1); ++ IBlockData iblockdata = iblockaccess.getType(blockposition_mutableblockposition); ++ ++ if (iblockdata.a(Blocks.CACTUS)) { ++ return PathType.DANGER_CACTUS; ++ } ++ ++ if (iblockdata.a(Blocks.SWEET_BERRY_BUSH)) { ++ return PathType.DANGER_OTHER; ++ } ++ ++ if (a(iblockdata)) { ++ return PathType.DANGER_FIRE; ++ } ++ ++ if (iblockaccess.getFluid(blockposition_mutableblockposition).a((Tag) TagsFluid.WATER)) { ++ return PathType.WATER_BORDER; ++ } ++ } ++ } ++ } ++ } ++ ++ return pathtype; ++ } ++ ++ protected static PathType b(IBlockAccess iblockaccess, BlockPosition blockposition) { ++ IBlockData iblockdata = iblockaccess.getType(blockposition); ++ Block block = iblockdata.getBlock(); ++ Material material = iblockdata.getMaterial(); ++ ++ if (iblockdata.isAir()) { ++ return PathType.OPEN; ++ } else if (!iblockdata.a((Tag) TagsBlock.TRAPDOORS) && !iblockdata.a(Blocks.LILY_PAD)) { ++ if (iblockdata.a(Blocks.CACTUS)) { ++ return PathType.DAMAGE_CACTUS; ++ } else if (iblockdata.a(Blocks.SWEET_BERRY_BUSH)) { ++ return PathType.DAMAGE_OTHER; ++ } else if (iblockdata.a(Blocks.HONEY_BLOCK)) { ++ return PathType.STICKY_HONEY; ++ } else if (iblockdata.a(Blocks.COCOA)) { ++ return PathType.COCOA; ++ } else { ++ Fluid fluid = iblockaccess.getFluid(blockposition); ++ ++ return fluid.a((Tag) TagsFluid.WATER) ? PathType.WATER : (fluid.a((Tag) TagsFluid.LAVA) ? PathType.LAVA : (a(iblockdata) ? PathType.DAMAGE_FIRE : (BlockDoor.l(iblockdata) && !(Boolean) iblockdata.get(BlockDoor.OPEN) ? PathType.DOOR_WOOD_CLOSED : (block instanceof BlockDoor && material == Material.ORE && !(Boolean) iblockdata.get(BlockDoor.OPEN) ? PathType.DOOR_IRON_CLOSED : (block instanceof BlockDoor && (Boolean) iblockdata.get(BlockDoor.OPEN) ? PathType.DOOR_OPEN : (block instanceof BlockMinecartTrackAbstract ? PathType.RAIL : (block instanceof BlockLeaves ? PathType.LEAVES : (!block.a((Tag) TagsBlock.FENCES) && !block.a((Tag) TagsBlock.WALLS) && (!(block instanceof BlockFenceGate) || (Boolean) iblockdata.get(BlockFenceGate.OPEN)) ? (!iblockdata.a(iblockaccess, blockposition, PathMode.LAND) ? PathType.BLOCKED : PathType.OPEN) : PathType.FENCE)))))))); ++ } ++ } else { ++ return PathType.TRAPDOOR; ++ } ++ } ++ ++ private static boolean a(IBlockData iblockdata) { ++ return iblockdata.a((Tag) TagsBlock.FIRE) || iblockdata.a(Blocks.LAVA) || iblockdata.a(Blocks.MAGMA_BLOCK) || BlockCampfire.g(iblockdata); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/storage/WorldData.java b/src/main/java/net/minecraft/world/level/storage/WorldData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..81ad90ba93481decdfaa38fc9fa81bca0e402781 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/storage/WorldData.java +@@ -0,0 +1,43 @@ ++package net.minecraft.world.level.storage; ++ ++import net.minecraft.CrashReportSystemDetails; ++import net.minecraft.world.EnumDifficulty; ++import net.minecraft.world.level.GameRules; ++ ++public interface WorldData { ++ ++ int a(); ++ ++ int b(); ++ ++ int c(); ++ ++ float d(); ++ ++ long getTime(); ++ ++ long getDayTime(); ++ ++ boolean isThundering(); ++ ++ boolean hasStorm(); ++ ++ void setStorm(boolean flag); ++ ++ boolean isHardcore(); ++ ++ GameRules q(); ++ ++ EnumDifficulty getDifficulty(); ++ ++ boolean isDifficultyLocked(); ++ ++ default void a(CrashReportSystemDetails crashreportsystemdetails) { ++ crashreportsystemdetails.a("Level spawn location", () -> { ++ return CrashReportSystemDetails.a(this.a(), this.b(), this.c()); ++ }); ++ crashreportsystemdetails.a("Level time", () -> { ++ return String.format("%d game time, %d day time", this.getTime(), this.getDayTime()); ++ }); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/storage/WorldPersistentData.java b/src/main/java/net/minecraft/world/level/storage/WorldPersistentData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..45c1d79e0bb2fcffea31513c3d003d28140146b9 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/storage/WorldPersistentData.java +@@ -0,0 +1,204 @@ ++package net.minecraft.world.level.storage; ++ ++import com.google.common.collect.Maps; ++import com.mojang.datafixers.DataFixer; ++import java.io.DataInput; ++import java.io.DataInputStream; ++import java.io.File; ++import java.io.FileInputStream; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.PushbackInputStream; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.function.Supplier; ++import javax.annotation.Nullable; ++import net.minecraft.SharedConstants; ++import net.minecraft.nbt.GameProfileSerializer; ++import net.minecraft.nbt.NBTCompressedStreamTools; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.util.datafix.DataFixTypes; ++import net.minecraft.world.level.saveddata.PersistentBase; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class WorldPersistentData { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ public final Map data = Maps.newHashMap(); ++ private final DataFixer c; ++ private final File d; ++ ++ public WorldPersistentData(File file, DataFixer datafixer) { ++ this.c = datafixer; ++ this.d = file; ++ } ++ ++ private File a(String s) { ++ return new File(this.d, s + ".dat"); ++ } ++ ++ public T a(Supplier supplier, String s) { ++ T t0 = this.b(supplier, s); ++ ++ if (t0 != null) { ++ return t0; ++ } else { ++ T t1 = (PersistentBase) supplier.get(); ++ ++ this.a(t1); ++ return t1; ++ } ++ } ++ ++ @Nullable ++ public T b(Supplier supplier, String s) { ++ PersistentBase persistentbase = (PersistentBase) this.data.get(s); ++ ++ if (persistentbase == null && !this.data.containsKey(s)) { ++ persistentbase = this.c(supplier, s); ++ this.data.put(s, persistentbase); ++ } ++ ++ return persistentbase; ++ } ++ ++ @Nullable ++ private T c(Supplier supplier, String s) { ++ try { ++ File file = this.a(s); ++ ++ if (file.exists()) { ++ T t0 = (PersistentBase) supplier.get(); ++ NBTTagCompound nbttagcompound = this.a(s, SharedConstants.getGameVersion().getWorldVersion()); ++ ++ t0.a(nbttagcompound.getCompound("data")); ++ return t0; ++ } ++ } catch (Exception exception) { ++ WorldPersistentData.LOGGER.error("Error loading saved data: {}", s, exception); ++ } ++ ++ return null; ++ } ++ ++ public void a(PersistentBase persistentbase) { ++ this.data.put(persistentbase.getId(), persistentbase); ++ } ++ ++ public NBTTagCompound a(String s, int i) throws IOException { ++ File file = this.a(s); ++ FileInputStream fileinputstream = new FileInputStream(file); ++ Throwable throwable = null; ++ ++ Object object; ++ ++ try { ++ PushbackInputStream pushbackinputstream = new PushbackInputStream(fileinputstream, 2); ++ Throwable throwable1 = null; ++ ++ try { ++ NBTTagCompound nbttagcompound; ++ ++ if (this.a(pushbackinputstream)) { ++ nbttagcompound = NBTCompressedStreamTools.a((InputStream) pushbackinputstream); ++ } else { ++ DataInputStream datainputstream = new DataInputStream(pushbackinputstream); ++ ++ object = null; ++ ++ try { ++ nbttagcompound = NBTCompressedStreamTools.a((DataInput) datainputstream); ++ } catch (Throwable throwable2) { ++ object = throwable2; ++ throw throwable2; ++ } finally { ++ if (datainputstream != null) { ++ if (object != null) { ++ try { ++ datainputstream.close(); ++ } catch (Throwable throwable3) { ++ ((Throwable) object).addSuppressed(throwable3); ++ } ++ } else { ++ datainputstream.close(); ++ } ++ } ++ ++ } ++ } ++ ++ int j = nbttagcompound.hasKeyOfType("DataVersion", 99) ? nbttagcompound.getInt("DataVersion") : 1343; ++ ++ object = GameProfileSerializer.a(this.c, DataFixTypes.SAVED_DATA, nbttagcompound, j, i); ++ } catch (Throwable throwable4) { ++ throwable1 = throwable4; ++ throw throwable4; ++ } finally { ++ if (pushbackinputstream != null) { ++ if (throwable1 != null) { ++ try { ++ pushbackinputstream.close(); ++ } catch (Throwable throwable5) { ++ throwable1.addSuppressed(throwable5); ++ } ++ } else { ++ pushbackinputstream.close(); ++ } ++ } ++ ++ } ++ } catch (Throwable throwable6) { ++ throwable = throwable6; ++ throw throwable6; ++ } finally { ++ if (fileinputstream != null) { ++ if (throwable != null) { ++ try { ++ fileinputstream.close(); ++ } catch (Throwable throwable7) { ++ throwable.addSuppressed(throwable7); ++ } ++ } else { ++ fileinputstream.close(); ++ } ++ } ++ ++ } ++ ++ return (NBTTagCompound) object; ++ } ++ ++ private boolean a(PushbackInputStream pushbackinputstream) throws IOException { ++ byte[] abyte = new byte[2]; ++ boolean flag = false; ++ int i = pushbackinputstream.read(abyte, 0, 2); ++ ++ if (i == 2) { ++ int j = (abyte[1] & 255) << 8 | abyte[0] & 255; ++ ++ if (j == 35615) { ++ flag = true; ++ } ++ } ++ ++ if (i != 0) { ++ pushbackinputstream.unread(abyte, 0, i); ++ } ++ ++ return flag; ++ } ++ ++ public void a() { ++ Iterator iterator = this.data.values().iterator(); ++ ++ while (iterator.hasNext()) { ++ PersistentBase persistentbase = (PersistentBase) iterator.next(); ++ ++ if (persistentbase != null) { ++ persistentbase.a(this.a(persistentbase.getId())); ++ } ++ } ++ ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootSelectorEntry.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootSelectorEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0e3fe138fc11bd7e648296922c651cecaab8e71e +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootSelectorEntry.java +@@ -0,0 +1,162 @@ ++package net.minecraft.world.level.storage.loot.entries; ++ ++import com.google.common.collect.Lists; ++import com.google.gson.JsonDeserializationContext; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonSerializationContext; ++import java.util.List; ++import java.util.function.BiFunction; ++import java.util.function.Consumer; ++import net.minecraft.util.ChatDeserializer; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.storage.loot.LootCollector; ++import net.minecraft.world.level.storage.loot.LootTableInfo; ++import net.minecraft.world.level.storage.loot.functions.LootItemFunction; ++import net.minecraft.world.level.storage.loot.functions.LootItemFunctionUser; ++import net.minecraft.world.level.storage.loot.functions.LootItemFunctions; ++import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; ++import org.apache.commons.lang3.ArrayUtils; ++ ++public abstract class LootSelectorEntry extends LootEntryAbstract { ++ ++ protected final int c; ++ protected final int e; ++ protected final LootItemFunction[] f; ++ private final BiFunction g; ++ private final LootEntry h = new LootSelectorEntry.c() { ++ @Override ++ public void a(Consumer consumer, LootTableInfo loottableinfo) { ++ LootSelectorEntry.this.a(LootItemFunction.a(LootSelectorEntry.this.g, consumer, loottableinfo), loottableinfo); ++ } ++ }; ++ ++ protected LootSelectorEntry(int i, int j, LootItemCondition[] alootitemcondition, LootItemFunction[] alootitemfunction) { ++ super(alootitemcondition); ++ this.c = i; ++ this.e = j; ++ this.f = alootitemfunction; ++ this.g = LootItemFunctions.a(alootitemfunction); ++ } ++ ++ @Override ++ public void a(LootCollector lootcollector) { ++ super.a(lootcollector); ++ ++ for (int i = 0; i < this.f.length; ++i) { ++ this.f[i].a(lootcollector.b(".functions[" + i + "]")); ++ } ++ ++ } ++ ++ protected abstract void a(Consumer consumer, LootTableInfo loottableinfo); ++ ++ @Override ++ public boolean expand(LootTableInfo loottableinfo, Consumer consumer) { ++ if (this.a(loottableinfo)) { ++ consumer.accept(this.h); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ public static LootSelectorEntry.a a(LootSelectorEntry.d lootselectorentry_d) { ++ return new LootSelectorEntry.b(lootselectorentry_d); ++ } ++ ++ public abstract static class e extends LootEntryAbstract.Serializer { ++ ++ public e() {} ++ ++ public void a(JsonObject jsonobject, T t0, JsonSerializationContext jsonserializationcontext) { ++ if (t0.c != 1) { ++ jsonobject.addProperty("weight", t0.c); ++ } ++ ++ if (t0.e != 0) { ++ jsonobject.addProperty("quality", t0.e); ++ } ++ ++ if (!ArrayUtils.isEmpty(t0.f)) { ++ jsonobject.add("functions", jsonserializationcontext.serialize(t0.f)); ++ } ++ ++ } ++ ++ @Override ++ public final T deserializeType(JsonObject jsonobject, JsonDeserializationContext jsondeserializationcontext, LootItemCondition[] alootitemcondition) { ++ int i = ChatDeserializer.a(jsonobject, "weight", (int) 1); ++ int j = ChatDeserializer.a(jsonobject, "quality", (int) 0); ++ LootItemFunction[] alootitemfunction = (LootItemFunction[]) ChatDeserializer.a(jsonobject, "functions", new LootItemFunction[0], jsondeserializationcontext, LootItemFunction[].class); ++ ++ return this.b(jsonobject, jsondeserializationcontext, i, j, alootitemcondition, alootitemfunction); ++ } ++ ++ protected abstract T b(JsonObject jsonobject, JsonDeserializationContext jsondeserializationcontext, int i, int j, LootItemCondition[] alootitemcondition, LootItemFunction[] alootitemfunction); ++ } ++ ++ static class b extends LootSelectorEntry.a { ++ ++ private final LootSelectorEntry.d c; ++ ++ public b(LootSelectorEntry.d lootselectorentry_d) { ++ this.c = lootselectorentry_d; ++ } ++ ++ @Override ++ protected LootSelectorEntry.b d() { ++ return this; ++ } ++ ++ @Override ++ public LootEntryAbstract b() { ++ return this.c.build(this.a, this.b, this.f(), this.a()); ++ } ++ } ++ ++ @FunctionalInterface ++ public interface d { ++ ++ LootSelectorEntry build(int i, int j, LootItemCondition[] alootitemcondition, LootItemFunction[] alootitemfunction); ++ } ++ ++ public abstract static class a> extends LootEntryAbstract.a implements LootItemFunctionUser { ++ ++ protected int a = 1; ++ protected int b = 0; ++ private final List c = Lists.newArrayList(); ++ ++ public a() {} ++ ++ @Override ++ public T b(LootItemFunction.a lootitemfunction_a) { ++ this.c.add(lootitemfunction_a.b()); ++ return (LootSelectorEntry.a) this.d(); ++ } ++ ++ protected LootItemFunction[] a() { ++ return (LootItemFunction[]) this.c.toArray(new LootItemFunction[0]); ++ } ++ ++ public T a(int i) { ++ this.a = i; ++ return (LootSelectorEntry.a) this.d(); ++ } ++ ++ public T b(int i) { ++ this.b = i; ++ return (LootSelectorEntry.a) this.d(); ++ } ++ } ++ ++ public abstract class c implements LootEntry { ++ ++ protected c() {} ++ ++ @Override ++ public int a(float f) { ++ return Math.max(MathHelper.d((float) LootSelectorEntry.this.c + (float) LootSelectorEntry.this.e * f), 0); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootItemFunctionExplorationMap.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootItemFunctionExplorationMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f516e7440ed306b1ace9b35ae82f70ca69df51f3 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootItemFunctionExplorationMap.java +@@ -0,0 +1,194 @@ ++package net.minecraft.world.level.storage.loot.functions; ++ ++import com.google.common.collect.ImmutableSet; ++import com.google.gson.JsonDeserializationContext; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonSerializationContext; ++import java.util.Locale; ++import java.util.Set; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.network.chat.ChatMessage; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.util.ChatDeserializer; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.ItemWorldMap; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.level.levelgen.feature.StructureGenerator; ++import net.minecraft.world.level.saveddata.maps.MapIcon; ++import net.minecraft.world.level.saveddata.maps.WorldMap; ++import net.minecraft.world.level.storage.loot.LootTableInfo; ++import net.minecraft.world.level.storage.loot.parameters.LootContextParameter; ++import net.minecraft.world.level.storage.loot.parameters.LootContextParameters; ++import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; ++import net.minecraft.world.phys.Vec3D; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class LootItemFunctionExplorationMap extends LootItemFunctionConditional { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ public static final StructureGenerator a = StructureGenerator.BURIED_TREASURE; ++ public static final MapIcon.Type b = MapIcon.Type.MANSION; ++ private final StructureGenerator e; ++ private final MapIcon.Type f; ++ private final byte g; ++ private final int h; ++ private final boolean i; ++ ++ private LootItemFunctionExplorationMap(LootItemCondition[] alootitemcondition, StructureGenerator structuregenerator, MapIcon.Type mapicon_type, byte b0, int i, boolean flag) { ++ super(alootitemcondition); ++ this.e = structuregenerator; ++ this.f = mapicon_type; ++ this.g = b0; ++ this.h = i; ++ this.i = flag; ++ } ++ ++ @Override ++ public LootItemFunctionType b() { ++ return LootItemFunctions.k; ++ } ++ ++ @Override ++ public Set> a() { ++ return ImmutableSet.of(LootContextParameters.ORIGIN); ++ } ++ ++ @Override ++ public ItemStack a(ItemStack itemstack, LootTableInfo loottableinfo) { ++ if (itemstack.getItem() != Items.MAP) { ++ return itemstack; ++ } else { ++ Vec3D vec3d = (Vec3D) loottableinfo.getContextParameter(LootContextParameters.ORIGIN); ++ ++ if (vec3d != null) { ++ WorldServer worldserver = loottableinfo.getWorld(); ++ BlockPosition blockposition = worldserver.a(this.e, new BlockPosition(vec3d), this.h, this.i); ++ ++ if (blockposition != null) { ++ ItemStack itemstack1 = ItemWorldMap.createFilledMapView(worldserver, blockposition.getX(), blockposition.getZ(), this.g, true, true); ++ ++ ItemWorldMap.applySepiaFilter(worldserver, itemstack1); ++ WorldMap.decorateMap(itemstack1, blockposition, "+", this.f); ++ itemstack1.a((IChatBaseComponent) (new ChatMessage("filled_map." + this.e.i().toLowerCase(Locale.ROOT)))); ++ return itemstack1; ++ } ++ } ++ ++ return itemstack; ++ } ++ } ++ ++ public static LootItemFunctionExplorationMap.a c() { ++ return new LootItemFunctionExplorationMap.a(); ++ } ++ ++ public static class b extends LootItemFunctionConditional.c { ++ ++ public b() {} ++ ++ public void a(JsonObject jsonobject, LootItemFunctionExplorationMap lootitemfunctionexplorationmap, JsonSerializationContext jsonserializationcontext) { ++ super.a(jsonobject, (LootItemFunctionConditional) lootitemfunctionexplorationmap, jsonserializationcontext); ++ if (!lootitemfunctionexplorationmap.e.equals(LootItemFunctionExplorationMap.a)) { ++ jsonobject.add("destination", jsonserializationcontext.serialize(lootitemfunctionexplorationmap.e.i())); ++ } ++ ++ if (lootitemfunctionexplorationmap.f != LootItemFunctionExplorationMap.b) { ++ jsonobject.add("decoration", jsonserializationcontext.serialize(lootitemfunctionexplorationmap.f.toString().toLowerCase(Locale.ROOT))); ++ } ++ ++ if (lootitemfunctionexplorationmap.g != 2) { ++ jsonobject.addProperty("zoom", lootitemfunctionexplorationmap.g); ++ } ++ ++ if (lootitemfunctionexplorationmap.h != 50) { ++ jsonobject.addProperty("search_radius", lootitemfunctionexplorationmap.h); ++ } ++ ++ if (!lootitemfunctionexplorationmap.i) { ++ jsonobject.addProperty("skip_existing_chunks", lootitemfunctionexplorationmap.i); ++ } ++ ++ } ++ ++ @Override ++ public LootItemFunctionExplorationMap b(JsonObject jsonobject, JsonDeserializationContext jsondeserializationcontext, LootItemCondition[] alootitemcondition) { ++ StructureGenerator structuregenerator = a(jsonobject); ++ String s = jsonobject.has("decoration") ? ChatDeserializer.h(jsonobject, "decoration") : "mansion"; ++ MapIcon.Type mapicon_type = LootItemFunctionExplorationMap.b; ++ ++ try { ++ mapicon_type = MapIcon.Type.valueOf(s.toUpperCase(Locale.ROOT)); ++ } catch (IllegalArgumentException illegalargumentexception) { ++ LootItemFunctionExplorationMap.LOGGER.error("Error while parsing loot table decoration entry. Found {}. Defaulting to " + LootItemFunctionExplorationMap.b, s); ++ } ++ ++ byte b0 = ChatDeserializer.a(jsonobject, "zoom", (byte) 2); ++ int i = ChatDeserializer.a(jsonobject, "search_radius", (int) 50); ++ boolean flag = ChatDeserializer.a(jsonobject, "skip_existing_chunks", true); ++ ++ return new LootItemFunctionExplorationMap(alootitemcondition, structuregenerator, mapicon_type, b0, i, flag); ++ } ++ ++ private static StructureGenerator a(JsonObject jsonobject) { ++ if (jsonobject.has("destination")) { ++ String s = ChatDeserializer.h(jsonobject, "destination"); ++ StructureGenerator structuregenerator = (StructureGenerator) StructureGenerator.a.get(s.toLowerCase(Locale.ROOT)); ++ ++ if (structuregenerator != null) { ++ return structuregenerator; ++ } ++ } ++ ++ return LootItemFunctionExplorationMap.a; ++ } ++ } ++ ++ public static class a extends LootItemFunctionConditional.a { ++ ++ private StructureGenerator a; ++ private MapIcon.Type b; ++ private byte c; ++ private int d; ++ private boolean e; ++ ++ public a() { ++ this.a = LootItemFunctionExplorationMap.a; ++ this.b = LootItemFunctionExplorationMap.b; ++ this.c = 2; ++ this.d = 50; ++ this.e = true; ++ } ++ ++ @Override ++ protected LootItemFunctionExplorationMap.a d() { ++ return this; ++ } ++ ++ public LootItemFunctionExplorationMap.a a(StructureGenerator structuregenerator) { ++ this.a = structuregenerator; ++ return this; ++ } ++ ++ public LootItemFunctionExplorationMap.a a(MapIcon.Type mapicon_type) { ++ this.b = mapicon_type; ++ return this; ++ } ++ ++ public LootItemFunctionExplorationMap.a a(byte b0) { ++ this.c = b0; ++ return this; ++ } ++ ++ public LootItemFunctionExplorationMap.a a(boolean flag) { ++ this.e = flag; ++ return this; ++ } ++ ++ @Override ++ public LootItemFunction b() { ++ return new LootItemFunctionExplorationMap(this.g(), this.a, this.b, this.c, this.d, this.e); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java b/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java +new file mode 100644 +index 0000000000000000000000000000000000000000..633a484cebc99f4a2f071b7f84b0b63d0ec3f985 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java +@@ -0,0 +1,324 @@ ++package net.minecraft.world.phys; ++ ++import java.util.Iterator; ++import java.util.Optional; ++import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.level.levelgen.structure.StructureBoundingBox; ++ ++public class AxisAlignedBB { ++ ++ public final double minX; ++ public final double minY; ++ public final double minZ; ++ public final double maxX; ++ public final double maxY; ++ public final double maxZ; ++ ++ public AxisAlignedBB(double d0, double d1, double d2, double d3, double d4, double d5) { ++ this.minX = Math.min(d0, d3); ++ this.minY = Math.min(d1, d4); ++ this.minZ = Math.min(d2, d5); ++ this.maxX = Math.max(d0, d3); ++ this.maxY = Math.max(d1, d4); ++ this.maxZ = Math.max(d2, d5); ++ } ++ ++ public AxisAlignedBB(BlockPosition blockposition) { ++ this((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), (double) (blockposition.getX() + 1), (double) (blockposition.getY() + 1), (double) (blockposition.getZ() + 1)); ++ } ++ ++ public AxisAlignedBB(BlockPosition blockposition, BlockPosition blockposition1) { ++ this((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), (double) blockposition1.getX(), (double) blockposition1.getY(), (double) blockposition1.getZ()); ++ } ++ ++ public AxisAlignedBB(Vec3D vec3d, Vec3D vec3d1) { ++ this(vec3d.x, vec3d.y, vec3d.z, vec3d1.x, vec3d1.y, vec3d1.z); ++ } ++ ++ public static AxisAlignedBB a(StructureBoundingBox structureboundingbox) { ++ return new AxisAlignedBB((double) structureboundingbox.a, (double) structureboundingbox.b, (double) structureboundingbox.c, (double) (structureboundingbox.d + 1), (double) (structureboundingbox.e + 1), (double) (structureboundingbox.f + 1)); ++ } ++ ++ public static AxisAlignedBB a(Vec3D vec3d) { ++ return new AxisAlignedBB(vec3d.x, vec3d.y, vec3d.z, vec3d.x + 1.0D, vec3d.y + 1.0D, vec3d.z + 1.0D); ++ } ++ ++ public double a(EnumDirection.EnumAxis enumdirection_enumaxis) { ++ return enumdirection_enumaxis.a(this.minX, this.minY, this.minZ); ++ } ++ ++ public double b(EnumDirection.EnumAxis enumdirection_enumaxis) { ++ return enumdirection_enumaxis.a(this.maxX, this.maxY, this.maxZ); ++ } ++ ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } else if (!(object instanceof AxisAlignedBB)) { ++ return false; ++ } else { ++ AxisAlignedBB axisalignedbb = (AxisAlignedBB) object; ++ ++ return Double.compare(axisalignedbb.minX, this.minX) != 0 ? false : (Double.compare(axisalignedbb.minY, this.minY) != 0 ? false : (Double.compare(axisalignedbb.minZ, this.minZ) != 0 ? false : (Double.compare(axisalignedbb.maxX, this.maxX) != 0 ? false : (Double.compare(axisalignedbb.maxY, this.maxY) != 0 ? false : Double.compare(axisalignedbb.maxZ, this.maxZ) == 0)))); ++ } ++ } ++ ++ public int hashCode() { ++ long i = Double.doubleToLongBits(this.minX); ++ int j = (int) (i ^ i >>> 32); ++ ++ i = Double.doubleToLongBits(this.minY); ++ j = 31 * j + (int) (i ^ i >>> 32); ++ i = Double.doubleToLongBits(this.minZ); ++ j = 31 * j + (int) (i ^ i >>> 32); ++ i = Double.doubleToLongBits(this.maxX); ++ j = 31 * j + (int) (i ^ i >>> 32); ++ i = Double.doubleToLongBits(this.maxY); ++ j = 31 * j + (int) (i ^ i >>> 32); ++ i = Double.doubleToLongBits(this.maxZ); ++ j = 31 * j + (int) (i ^ i >>> 32); ++ return j; ++ } ++ ++ public AxisAlignedBB a(double d0, double d1, double d2) { ++ double d3 = this.minX; ++ double d4 = this.minY; ++ double d5 = this.minZ; ++ double d6 = this.maxX; ++ double d7 = this.maxY; ++ double d8 = this.maxZ; ++ ++ if (d0 < 0.0D) { ++ d3 -= d0; ++ } else if (d0 > 0.0D) { ++ d6 -= d0; ++ } ++ ++ if (d1 < 0.0D) { ++ d4 -= d1; ++ } else if (d1 > 0.0D) { ++ d7 -= d1; ++ } ++ ++ if (d2 < 0.0D) { ++ d5 -= d2; ++ } else if (d2 > 0.0D) { ++ d8 -= d2; ++ } ++ ++ return new AxisAlignedBB(d3, d4, d5, d6, d7, d8); ++ } ++ ++ public AxisAlignedBB b(Vec3D vec3d) { ++ return this.b(vec3d.x, vec3d.y, vec3d.z); ++ } ++ ++ public AxisAlignedBB b(double d0, double d1, double d2) { ++ double d3 = this.minX; ++ double d4 = this.minY; ++ double d5 = this.minZ; ++ double d6 = this.maxX; ++ double d7 = this.maxY; ++ double d8 = this.maxZ; ++ ++ if (d0 < 0.0D) { ++ d3 += d0; ++ } else if (d0 > 0.0D) { ++ d6 += d0; ++ } ++ ++ if (d1 < 0.0D) { ++ d4 += d1; ++ } else if (d1 > 0.0D) { ++ d7 += d1; ++ } ++ ++ if (d2 < 0.0D) { ++ d5 += d2; ++ } else if (d2 > 0.0D) { ++ d8 += d2; ++ } ++ ++ return new AxisAlignedBB(d3, d4, d5, d6, d7, d8); ++ } ++ ++ public AxisAlignedBB grow(double d0, double d1, double d2) { ++ double d3 = this.minX - d0; ++ double d4 = this.minY - d1; ++ double d5 = this.minZ - d2; ++ double d6 = this.maxX + d0; ++ double d7 = this.maxY + d1; ++ double d8 = this.maxZ + d2; ++ ++ return new AxisAlignedBB(d3, d4, d5, d6, d7, d8); ++ } ++ ++ public AxisAlignedBB g(double d0) { ++ return this.grow(d0, d0, d0); ++ } ++ ++ public AxisAlignedBB a(AxisAlignedBB axisalignedbb) { ++ double d0 = Math.max(this.minX, axisalignedbb.minX); ++ double d1 = Math.max(this.minY, axisalignedbb.minY); ++ double d2 = Math.max(this.minZ, axisalignedbb.minZ); ++ double d3 = Math.min(this.maxX, axisalignedbb.maxX); ++ double d4 = Math.min(this.maxY, axisalignedbb.maxY); ++ double d5 = Math.min(this.maxZ, axisalignedbb.maxZ); ++ ++ return new AxisAlignedBB(d0, d1, d2, d3, d4, d5); ++ } ++ ++ public AxisAlignedBB b(AxisAlignedBB axisalignedbb) { ++ double d0 = Math.min(this.minX, axisalignedbb.minX); ++ double d1 = Math.min(this.minY, axisalignedbb.minY); ++ double d2 = Math.min(this.minZ, axisalignedbb.minZ); ++ double d3 = Math.max(this.maxX, axisalignedbb.maxX); ++ double d4 = Math.max(this.maxY, axisalignedbb.maxY); ++ double d5 = Math.max(this.maxZ, axisalignedbb.maxZ); ++ ++ return new AxisAlignedBB(d0, d1, d2, d3, d4, d5); ++ } ++ ++ public AxisAlignedBB d(double d0, double d1, double d2) { ++ return new AxisAlignedBB(this.minX + d0, this.minY + d1, this.minZ + d2, this.maxX + d0, this.maxY + d1, this.maxZ + d2); ++ } ++ ++ public AxisAlignedBB a(BlockPosition blockposition) { ++ return new AxisAlignedBB(this.minX + (double) blockposition.getX(), this.minY + (double) blockposition.getY(), this.minZ + (double) blockposition.getZ(), this.maxX + (double) blockposition.getX(), this.maxY + (double) blockposition.getY(), this.maxZ + (double) blockposition.getZ()); ++ } ++ ++ public AxisAlignedBB c(Vec3D vec3d) { ++ return this.d(vec3d.x, vec3d.y, vec3d.z); ++ } ++ ++ public boolean c(AxisAlignedBB axisalignedbb) { ++ return this.a(axisalignedbb.minX, axisalignedbb.minY, axisalignedbb.minZ, axisalignedbb.maxX, axisalignedbb.maxY, axisalignedbb.maxZ); ++ } ++ ++ public boolean a(double d0, double d1, double d2, double d3, double d4, double d5) { ++ return this.minX < d3 && this.maxX > d0 && this.minY < d4 && this.maxY > d1 && this.minZ < d5 && this.maxZ > d2; ++ } ++ ++ public boolean d(Vec3D vec3d) { ++ return this.e(vec3d.x, vec3d.y, vec3d.z); ++ } ++ ++ public boolean e(double d0, double d1, double d2) { ++ return d0 >= this.minX && d0 < this.maxX && d1 >= this.minY && d1 < this.maxY && d2 >= this.minZ && d2 < this.maxZ; ++ } ++ ++ public double a() { ++ double d0 = this.b(); ++ double d1 = this.c(); ++ double d2 = this.d(); ++ ++ return (d0 + d1 + d2) / 3.0D; ++ } ++ ++ public double b() { ++ return this.maxX - this.minX; ++ } ++ ++ public double c() { ++ return this.maxY - this.minY; ++ } ++ ++ public double d() { ++ return this.maxZ - this.minZ; ++ } ++ ++ public AxisAlignedBB shrink(double d0) { ++ return this.g(-d0); ++ } ++ ++ public Optional b(Vec3D vec3d, Vec3D vec3d1) { ++ double[] adouble = new double[]{1.0D}; ++ double d0 = vec3d1.x - vec3d.x; ++ double d1 = vec3d1.y - vec3d.y; ++ double d2 = vec3d1.z - vec3d.z; ++ EnumDirection enumdirection = a(this, vec3d, adouble, (EnumDirection) null, d0, d1, d2); ++ ++ if (enumdirection == null) { ++ return Optional.empty(); ++ } else { ++ double d3 = adouble[0]; ++ ++ return Optional.of(vec3d.add(d3 * d0, d3 * d1, d3 * d2)); ++ } ++ } ++ ++ @Nullable ++ public static MovingObjectPositionBlock a(Iterable iterable, Vec3D vec3d, Vec3D vec3d1, BlockPosition blockposition) { ++ double[] adouble = new double[]{1.0D}; ++ EnumDirection enumdirection = null; ++ double d0 = vec3d1.x - vec3d.x; ++ double d1 = vec3d1.y - vec3d.y; ++ double d2 = vec3d1.z - vec3d.z; ++ ++ AxisAlignedBB axisalignedbb; ++ ++ for (Iterator iterator = iterable.iterator(); iterator.hasNext(); enumdirection = a(axisalignedbb.a(blockposition), vec3d, adouble, enumdirection, d0, d1, d2)) { ++ axisalignedbb = (AxisAlignedBB) iterator.next(); ++ } ++ ++ if (enumdirection == null) { ++ return null; ++ } else { ++ double d3 = adouble[0]; ++ ++ return new MovingObjectPositionBlock(vec3d.add(d3 * d0, d3 * d1, d3 * d2), enumdirection, blockposition, false); ++ } ++ } ++ ++ @Nullable ++ private static EnumDirection a(AxisAlignedBB axisalignedbb, Vec3D vec3d, double[] adouble, @Nullable EnumDirection enumdirection, double d0, double d1, double d2) { ++ if (d0 > 1.0E-7D) { ++ enumdirection = a(adouble, enumdirection, d0, d1, d2, axisalignedbb.minX, axisalignedbb.minY, axisalignedbb.maxY, axisalignedbb.minZ, axisalignedbb.maxZ, EnumDirection.WEST, vec3d.x, vec3d.y, vec3d.z); ++ } else if (d0 < -1.0E-7D) { ++ enumdirection = a(adouble, enumdirection, d0, d1, d2, axisalignedbb.maxX, axisalignedbb.minY, axisalignedbb.maxY, axisalignedbb.minZ, axisalignedbb.maxZ, EnumDirection.EAST, vec3d.x, vec3d.y, vec3d.z); ++ } ++ ++ if (d1 > 1.0E-7D) { ++ enumdirection = a(adouble, enumdirection, d1, d2, d0, axisalignedbb.minY, axisalignedbb.minZ, axisalignedbb.maxZ, axisalignedbb.minX, axisalignedbb.maxX, EnumDirection.DOWN, vec3d.y, vec3d.z, vec3d.x); ++ } else if (d1 < -1.0E-7D) { ++ enumdirection = a(adouble, enumdirection, d1, d2, d0, axisalignedbb.maxY, axisalignedbb.minZ, axisalignedbb.maxZ, axisalignedbb.minX, axisalignedbb.maxX, EnumDirection.UP, vec3d.y, vec3d.z, vec3d.x); ++ } ++ ++ if (d2 > 1.0E-7D) { ++ enumdirection = a(adouble, enumdirection, d2, d0, d1, axisalignedbb.minZ, axisalignedbb.minX, axisalignedbb.maxX, axisalignedbb.minY, axisalignedbb.maxY, EnumDirection.NORTH, vec3d.z, vec3d.x, vec3d.y); ++ } else if (d2 < -1.0E-7D) { ++ enumdirection = a(adouble, enumdirection, d2, d0, d1, axisalignedbb.maxZ, axisalignedbb.minX, axisalignedbb.maxX, axisalignedbb.minY, axisalignedbb.maxY, EnumDirection.SOUTH, vec3d.z, vec3d.x, vec3d.y); ++ } ++ ++ return enumdirection; ++ } ++ ++ @Nullable ++ private static EnumDirection a(double[] adouble, @Nullable EnumDirection enumdirection, double d0, double d1, double d2, double d3, double d4, double d5, double d6, double d7, EnumDirection enumdirection1, double d8, double d9, double d10) { ++ double d11 = (d3 - d8) / d0; ++ double d12 = d9 + d11 * d1; ++ double d13 = d10 + d11 * d2; ++ ++ if (0.0D < d11 && d11 < adouble[0] && d4 - 1.0E-7D < d12 && d12 < d5 + 1.0E-7D && d6 - 1.0E-7D < d13 && d13 < d7 + 1.0E-7D) { ++ adouble[0] = d11; ++ return enumdirection1; ++ } else { ++ return enumdirection; ++ } ++ } ++ ++ public String toString() { ++ return "AABB[" + this.minX + ", " + this.minY + ", " + this.minZ + "] -> [" + this.maxX + ", " + this.maxY + ", " + this.maxZ + "]"; ++ } ++ ++ public Vec3D f() { ++ return new Vec3D(MathHelper.d(0.5D, this.minX, this.maxX), MathHelper.d(0.5D, this.minY, this.maxY), MathHelper.d(0.5D, this.minZ, this.maxZ)); ++ } ++ ++ public static AxisAlignedBB g(double d0, double d1, double d2) { ++ return new AxisAlignedBB(-d0 / 2.0D, -d1 / 2.0D, -d2 / 2.0D, d0 / 2.0D, d1 / 2.0D, d2 / 2.0D); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/phys/Vec3D.java b/src/main/java/net/minecraft/world/phys/Vec3D.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b71e119eed6fa283d99dc033144c8be7b336d9c4 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/phys/Vec3D.java +@@ -0,0 +1,198 @@ ++package net.minecraft.world.phys; ++ ++import com.mojang.math.Vector3fa; ++import java.util.EnumSet; ++import net.minecraft.core.BaseBlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.core.IPosition; ++import net.minecraft.util.MathHelper; ++ ++public class Vec3D implements IPosition { ++ ++ public static final Vec3D ORIGIN = new Vec3D(0.0D, 0.0D, 0.0D); ++ public final double x; ++ public final double y; ++ public final double z; ++ ++ public static Vec3D a(BaseBlockPosition baseblockposition) { ++ return new Vec3D((double) baseblockposition.getX() + 0.5D, (double) baseblockposition.getY() + 0.5D, (double) baseblockposition.getZ() + 0.5D); ++ } ++ ++ public static Vec3D b(BaseBlockPosition baseblockposition) { ++ return new Vec3D((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ()); ++ } ++ ++ public static Vec3D c(BaseBlockPosition baseblockposition) { ++ return new Vec3D((double) baseblockposition.getX() + 0.5D, (double) baseblockposition.getY(), (double) baseblockposition.getZ() + 0.5D); ++ } ++ ++ public static Vec3D a(BaseBlockPosition baseblockposition, double d0) { ++ return new Vec3D((double) baseblockposition.getX() + 0.5D, (double) baseblockposition.getY() + d0, (double) baseblockposition.getZ() + 0.5D); ++ } ++ ++ public Vec3D(double d0, double d1, double d2) { ++ this.x = d0; ++ this.y = d1; ++ this.z = d2; ++ } ++ ++ public Vec3D(Vector3fa vector3fa) { ++ this((double) vector3fa.a(), (double) vector3fa.b(), (double) vector3fa.c()); ++ } ++ ++ public Vec3D a(Vec3D vec3d) { ++ return new Vec3D(vec3d.x - this.x, vec3d.y - this.y, vec3d.z - this.z); ++ } ++ ++ public Vec3D d() { ++ double d0 = (double) MathHelper.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); ++ ++ return d0 < 1.0E-4D ? Vec3D.ORIGIN : new Vec3D(this.x / d0, this.y / d0, this.z / d0); ++ } ++ ++ public double b(Vec3D vec3d) { ++ return this.x * vec3d.x + this.y * vec3d.y + this.z * vec3d.z; ++ } ++ ++ public Vec3D c(Vec3D vec3d) { ++ return new Vec3D(this.y * vec3d.z - this.z * vec3d.y, this.z * vec3d.x - this.x * vec3d.z, this.x * vec3d.y - this.y * vec3d.x); ++ } ++ ++ public Vec3D d(Vec3D vec3d) { ++ return this.a(vec3d.x, vec3d.y, vec3d.z); ++ } ++ ++ public Vec3D a(double d0, double d1, double d2) { ++ return this.add(-d0, -d1, -d2); ++ } ++ ++ public Vec3D e(Vec3D vec3d) { ++ return this.add(vec3d.x, vec3d.y, vec3d.z); ++ } ++ ++ public Vec3D add(double d0, double d1, double d2) { ++ return new Vec3D(this.x + d0, this.y + d1, this.z + d2); ++ } ++ ++ public boolean a(IPosition iposition, double d0) { ++ return this.c(iposition.getX(), iposition.getY(), iposition.getZ()) < d0 * d0; ++ } ++ ++ public double f(Vec3D vec3d) { ++ double d0 = vec3d.x - this.x; ++ double d1 = vec3d.y - this.y; ++ double d2 = vec3d.z - this.z; ++ ++ return (double) MathHelper.sqrt(d0 * d0 + d1 * d1 + d2 * d2); ++ } ++ ++ public double distanceSquared(Vec3D vec3d) { ++ double d0 = vec3d.x - this.x; ++ double d1 = vec3d.y - this.y; ++ double d2 = vec3d.z - this.z; ++ ++ return d0 * d0 + d1 * d1 + d2 * d2; ++ } ++ ++ public double c(double d0, double d1, double d2) { ++ double d3 = d0 - this.x; ++ double d4 = d1 - this.y; ++ double d5 = d2 - this.z; ++ ++ return d3 * d3 + d4 * d4 + d5 * d5; ++ } ++ ++ public Vec3D a(double d0) { ++ return this.d(d0, d0, d0); ++ } ++ ++ public Vec3D h(Vec3D vec3d) { ++ return this.d(vec3d.x, vec3d.y, vec3d.z); ++ } ++ ++ public Vec3D d(double d0, double d1, double d2) { ++ return new Vec3D(this.x * d0, this.y * d1, this.z * d2); ++ } ++ ++ public double f() { ++ return (double) MathHelper.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); ++ } ++ ++ public double g() { ++ return this.x * this.x + this.y * this.y + this.z * this.z; ++ } ++ ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } else if (!(object instanceof Vec3D)) { ++ return false; ++ } else { ++ Vec3D vec3d = (Vec3D) object; ++ ++ return Double.compare(vec3d.x, this.x) != 0 ? false : (Double.compare(vec3d.y, this.y) != 0 ? false : Double.compare(vec3d.z, this.z) == 0); ++ } ++ } ++ ++ public int hashCode() { ++ long i = Double.doubleToLongBits(this.x); ++ int j = (int) (i ^ i >>> 32); ++ ++ i = Double.doubleToLongBits(this.y); ++ j = 31 * j + (int) (i ^ i >>> 32); ++ i = Double.doubleToLongBits(this.z); ++ j = 31 * j + (int) (i ^ i >>> 32); ++ return j; ++ } ++ ++ public String toString() { ++ return "(" + this.x + ", " + this.y + ", " + this.z + ")"; ++ } ++ ++ public Vec3D a(float f) { ++ float f1 = MathHelper.cos(f); ++ float f2 = MathHelper.sin(f); ++ double d0 = this.x; ++ double d1 = this.y * (double) f1 + this.z * (double) f2; ++ double d2 = this.z * (double) f1 - this.y * (double) f2; ++ ++ return new Vec3D(d0, d1, d2); ++ } ++ ++ public Vec3D b(float f) { ++ float f1 = MathHelper.cos(f); ++ float f2 = MathHelper.sin(f); ++ double d0 = this.x * (double) f1 + this.z * (double) f2; ++ double d1 = this.y; ++ double d2 = this.z * (double) f1 - this.x * (double) f2; ++ ++ return new Vec3D(d0, d1, d2); ++ } ++ ++ public Vec3D a(EnumSet enumset) { ++ double d0 = enumset.contains(EnumDirection.EnumAxis.X) ? (double) MathHelper.floor(this.x) : this.x; ++ double d1 = enumset.contains(EnumDirection.EnumAxis.Y) ? (double) MathHelper.floor(this.y) : this.y; ++ double d2 = enumset.contains(EnumDirection.EnumAxis.Z) ? (double) MathHelper.floor(this.z) : this.z; ++ ++ return new Vec3D(d0, d1, d2); ++ } ++ ++ public double a(EnumDirection.EnumAxis enumdirection_enumaxis) { ++ return enumdirection_enumaxis.a(this.x, this.y, this.z); ++ } ++ ++ @Override ++ public final double getX() { ++ return this.x; ++ } ++ ++ @Override ++ public final double getY() { ++ return this.y; ++ } ++ ++ @Override ++ public final double getZ() { ++ return this.z; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1b82349b96b3ec9490d06d1c1d1cbf2b1578d313 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +@@ -0,0 +1,218 @@ ++package net.minecraft.world.phys.shapes; ++ ++import com.google.common.collect.Lists; ++import com.google.common.math.DoubleMath; ++import it.unimi.dsi.fastutil.doubles.DoubleList; ++import java.util.List; ++import javax.annotation.Nullable; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumAxisCycle; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.phys.AxisAlignedBB; ++import net.minecraft.world.phys.MovingObjectPositionBlock; ++import net.minecraft.world.phys.Vec3D; ++ ++public abstract class VoxelShape { ++ ++ protected final VoxelShapeDiscrete a; ++ @Nullable ++ private VoxelShape[] b; ++ ++ VoxelShape(VoxelShapeDiscrete voxelshapediscrete) { ++ this.a = voxelshapediscrete; ++ } ++ ++ public double b(EnumDirection.EnumAxis enumdirection_enumaxis) { ++ int i = this.a.a(enumdirection_enumaxis); ++ ++ return i >= this.a.c(enumdirection_enumaxis) ? Double.POSITIVE_INFINITY : this.a(enumdirection_enumaxis, i); ++ } ++ ++ public double c(EnumDirection.EnumAxis enumdirection_enumaxis) { ++ int i = this.a.b(enumdirection_enumaxis); ++ ++ return i <= 0 ? Double.NEGATIVE_INFINITY : this.a(enumdirection_enumaxis, i); ++ } ++ ++ public AxisAlignedBB getBoundingBox() { ++ if (this.isEmpty()) { ++ throw (UnsupportedOperationException) SystemUtils.c((Throwable) (new UnsupportedOperationException("No bounds for empty shape."))); ++ } else { ++ return new AxisAlignedBB(this.b(EnumDirection.EnumAxis.X), this.b(EnumDirection.EnumAxis.Y), this.b(EnumDirection.EnumAxis.Z), this.c(EnumDirection.EnumAxis.X), this.c(EnumDirection.EnumAxis.Y), this.c(EnumDirection.EnumAxis.Z)); ++ } ++ } ++ ++ protected double a(EnumDirection.EnumAxis enumdirection_enumaxis, int i) { ++ return this.a(enumdirection_enumaxis).getDouble(i); ++ } ++ ++ protected abstract DoubleList a(EnumDirection.EnumAxis enumdirection_enumaxis); ++ ++ public boolean isEmpty() { ++ return this.a.a(); ++ } ++ ++ public VoxelShape a(double d0, double d1, double d2) { ++ return (VoxelShape) (this.isEmpty() ? VoxelShapes.a() : new VoxelShapeArray(this.a, new DoubleListOffset(this.a(EnumDirection.EnumAxis.X), d0), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Y), d1), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Z), d2))); ++ } ++ ++ public VoxelShape c() { ++ VoxelShape[] avoxelshape = new VoxelShape[]{VoxelShapes.a()}; ++ ++ this.b((d0, d1, d2, d3, d4, d5) -> { ++ avoxelshape[0] = VoxelShapes.b(avoxelshape[0], VoxelShapes.create(d0, d1, d2, d3, d4, d5), OperatorBoolean.OR); ++ }); ++ return avoxelshape[0]; ++ } ++ ++ public void b(VoxelShapes.a voxelshapes_a) { ++ DoubleList doublelist = this.a(EnumDirection.EnumAxis.X); ++ DoubleList doublelist1 = this.a(EnumDirection.EnumAxis.Y); ++ DoubleList doublelist2 = this.a(EnumDirection.EnumAxis.Z); ++ ++ this.a.b((i, j, k, l, i1, j1) -> { ++ voxelshapes_a.consume(doublelist.getDouble(i), doublelist1.getDouble(j), doublelist2.getDouble(k), doublelist.getDouble(l), doublelist1.getDouble(i1), doublelist2.getDouble(j1)); ++ }, true); ++ } ++ ++ public List d() { ++ List list = Lists.newArrayList(); ++ ++ this.b((d0, d1, d2, d3, d4, d5) -> { ++ list.add(new AxisAlignedBB(d0, d1, d2, d3, d4, d5)); ++ }); ++ return list; ++ } ++ ++ protected int a(EnumDirection.EnumAxis enumdirection_enumaxis, double d0) { ++ return MathHelper.a(0, this.a.c(enumdirection_enumaxis) + 1, (i) -> { ++ return i < 0 ? false : (i > this.a.c(enumdirection_enumaxis) ? true : d0 < this.a(enumdirection_enumaxis, i)); ++ }) - 1; ++ } ++ ++ protected boolean b(double d0, double d1, double d2) { ++ return this.a.c(this.a(EnumDirection.EnumAxis.X, d0), this.a(EnumDirection.EnumAxis.Y, d1), this.a(EnumDirection.EnumAxis.Z, d2)); ++ } ++ ++ @Nullable ++ public MovingObjectPositionBlock rayTrace(Vec3D vec3d, Vec3D vec3d1, BlockPosition blockposition) { ++ if (this.isEmpty()) { ++ return null; ++ } else { ++ Vec3D vec3d2 = vec3d1.d(vec3d); ++ ++ if (vec3d2.g() < 1.0E-7D) { ++ return null; ++ } else { ++ Vec3D vec3d3 = vec3d.e(vec3d2.a(0.001D)); ++ ++ return this.b(vec3d3.x - (double) blockposition.getX(), vec3d3.y - (double) blockposition.getY(), vec3d3.z - (double) blockposition.getZ()) ? new MovingObjectPositionBlock(vec3d3, EnumDirection.a(vec3d2.x, vec3d2.y, vec3d2.z).opposite(), blockposition, true) : AxisAlignedBB.a(this.d(), vec3d, vec3d1, blockposition); ++ } ++ } ++ } ++ ++ public VoxelShape a(EnumDirection enumdirection) { ++ if (!this.isEmpty() && this != VoxelShapes.b()) { ++ VoxelShape voxelshape; ++ ++ if (this.b != null) { ++ voxelshape = this.b[enumdirection.ordinal()]; ++ if (voxelshape != null) { ++ return voxelshape; ++ } ++ } else { ++ this.b = new VoxelShape[6]; ++ } ++ ++ voxelshape = this.b(enumdirection); ++ this.b[enumdirection.ordinal()] = voxelshape; ++ return voxelshape; ++ } else { ++ return this; ++ } ++ } ++ ++ private VoxelShape b(EnumDirection enumdirection) { ++ EnumDirection.EnumAxis enumdirection_enumaxis = enumdirection.n(); ++ EnumDirection.EnumAxisDirection enumdirection_enumaxisdirection = enumdirection.e(); ++ DoubleList doublelist = this.a(enumdirection_enumaxis); ++ ++ if (doublelist.size() == 2 && DoubleMath.fuzzyEquals(doublelist.getDouble(0), 0.0D, 1.0E-7D) && DoubleMath.fuzzyEquals(doublelist.getDouble(1), 1.0D, 1.0E-7D)) { ++ return this; ++ } else { ++ int i = this.a(enumdirection_enumaxis, enumdirection_enumaxisdirection == EnumDirection.EnumAxisDirection.POSITIVE ? 0.9999999D : 1.0E-7D); ++ ++ return new VoxelShapeSlice(this, enumdirection_enumaxis, i); ++ } ++ } ++ ++ public double a(EnumDirection.EnumAxis enumdirection_enumaxis, AxisAlignedBB axisalignedbb, double d0) { ++ return this.a(EnumAxisCycle.a(enumdirection_enumaxis, EnumDirection.EnumAxis.X), axisalignedbb, d0); ++ } ++ ++ protected double a(EnumAxisCycle enumaxiscycle, AxisAlignedBB axisalignedbb, double d0) { ++ if (this.isEmpty()) { ++ return d0; ++ } else if (Math.abs(d0) < 1.0E-7D) { ++ return 0.0D; ++ } else { ++ EnumAxisCycle enumaxiscycle1 = enumaxiscycle.a(); ++ EnumDirection.EnumAxis enumdirection_enumaxis = enumaxiscycle1.a(EnumDirection.EnumAxis.X); ++ EnumDirection.EnumAxis enumdirection_enumaxis1 = enumaxiscycle1.a(EnumDirection.EnumAxis.Y); ++ EnumDirection.EnumAxis enumdirection_enumaxis2 = enumaxiscycle1.a(EnumDirection.EnumAxis.Z); ++ double d1 = axisalignedbb.b(enumdirection_enumaxis); ++ double d2 = axisalignedbb.a(enumdirection_enumaxis); ++ int i = this.a(enumdirection_enumaxis, d2 + 1.0E-7D); ++ int j = this.a(enumdirection_enumaxis, d1 - 1.0E-7D); ++ int k = Math.max(0, this.a(enumdirection_enumaxis1, axisalignedbb.a(enumdirection_enumaxis1) + 1.0E-7D)); ++ int l = Math.min(this.a.c(enumdirection_enumaxis1), this.a(enumdirection_enumaxis1, axisalignedbb.b(enumdirection_enumaxis1) - 1.0E-7D) + 1); ++ int i1 = Math.max(0, this.a(enumdirection_enumaxis2, axisalignedbb.a(enumdirection_enumaxis2) + 1.0E-7D)); ++ int j1 = Math.min(this.a.c(enumdirection_enumaxis2), this.a(enumdirection_enumaxis2, axisalignedbb.b(enumdirection_enumaxis2) - 1.0E-7D) + 1); ++ int k1 = this.a.c(enumdirection_enumaxis); ++ double d3; ++ int l1; ++ int i2; ++ int j2; ++ ++ if (d0 > 0.0D) { ++ for (l1 = j + 1; l1 < k1; ++l1) { ++ for (i2 = k; i2 < l; ++i2) { ++ for (j2 = i1; j2 < j1; ++j2) { ++ if (this.a.a(enumaxiscycle1, l1, i2, j2)) { ++ d3 = this.a(enumdirection_enumaxis, l1) - d1; ++ if (d3 >= -1.0E-7D) { ++ d0 = Math.min(d0, d3); ++ } ++ ++ return d0; ++ } ++ } ++ } ++ } ++ } else if (d0 < 0.0D) { ++ for (l1 = i - 1; l1 >= 0; --l1) { ++ for (i2 = k; i2 < l; ++i2) { ++ for (j2 = i1; j2 < j1; ++j2) { ++ if (this.a.a(enumaxiscycle1, l1, i2, j2)) { ++ d3 = this.a(enumdirection_enumaxis, l1 + 1) - d2; ++ if (d3 <= 1.0E-7D) { ++ d0 = Math.max(d0, d3); ++ } ++ ++ return d0; ++ } ++ } ++ } ++ } ++ } ++ ++ return d0; ++ } ++ } ++ ++ public String toString() { ++ return this.isEmpty() ? "EMPTY" : "VoxelShape[" + this.getBoundingBox() + "]"; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeArray.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeArray.java +new file mode 100644 +index 0000000000000000000000000000000000000000..56f0b3a74f676d288d81671a4791337e169b9758 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeArray.java +@@ -0,0 +1,47 @@ ++package net.minecraft.world.phys.shapes; ++ ++import it.unimi.dsi.fastutil.doubles.DoubleArrayList; ++import it.unimi.dsi.fastutil.doubles.DoubleList; ++import java.util.Arrays; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.EnumDirection; ++ ++public final class VoxelShapeArray extends VoxelShape { ++ ++ private final DoubleList b; ++ private final DoubleList c; ++ private final DoubleList d; ++ ++ protected VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, double[] adouble, double[] adouble1, double[] adouble2) { ++ this(voxelshapediscrete, (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble, voxelshapediscrete.b() + 1)), (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble1, voxelshapediscrete.c() + 1)), (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble2, voxelshapediscrete.d() + 1))); ++ } ++ ++ VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, DoubleList doublelist, DoubleList doublelist1, DoubleList doublelist2) { ++ super(voxelshapediscrete); ++ int i = voxelshapediscrete.b() + 1; ++ int j = voxelshapediscrete.c() + 1; ++ int k = voxelshapediscrete.d() + 1; ++ ++ if (i == doublelist.size() && j == doublelist1.size() && k == doublelist2.size()) { ++ this.b = doublelist; ++ this.c = doublelist1; ++ this.d = doublelist2; ++ } else { ++ throw (IllegalArgumentException) SystemUtils.c((Throwable) (new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape."))); ++ } ++ } ++ ++ @Override ++ protected DoubleList a(EnumDirection.EnumAxis enumdirection_enumaxis) { ++ switch (enumdirection_enumaxis) { ++ case X: ++ return this.b; ++ case Y: ++ return this.c; ++ case Z: ++ return this.d; ++ default: ++ throw new IllegalArgumentException(); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMergerList.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMergerList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..afd30320da51bf467d66e94f682936ed8db96d90 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMergerList.java +@@ -0,0 +1,69 @@ ++package net.minecraft.world.phys.shapes; ++ ++import it.unimi.dsi.fastutil.doubles.DoubleArrayList; ++import it.unimi.dsi.fastutil.doubles.DoubleList; ++import it.unimi.dsi.fastutil.ints.IntArrayList; ++ ++public final class VoxelShapeMergerList implements VoxelShapeMerger { ++ ++ private final DoubleArrayList a; ++ private final IntArrayList b; ++ private final IntArrayList c; ++ ++ protected VoxelShapeMergerList(DoubleList doublelist, DoubleList doublelist1, boolean flag, boolean flag1) { ++ int i = 0; ++ int j = 0; ++ double d0 = Double.NaN; ++ int k = doublelist.size(); ++ int l = doublelist1.size(); ++ int i1 = k + l; ++ ++ this.a = new DoubleArrayList(i1); ++ this.b = new IntArrayList(i1); ++ this.c = new IntArrayList(i1); ++ ++ while (true) { ++ boolean flag2 = i < k; ++ boolean flag3 = j < l; ++ ++ if (!flag2 && !flag3) { ++ if (this.a.isEmpty()) { ++ this.a.add(Math.min(doublelist.getDouble(k - 1), doublelist1.getDouble(l - 1))); ++ } ++ ++ return; ++ } ++ ++ boolean flag4 = flag2 && (!flag3 || doublelist.getDouble(i) < doublelist1.getDouble(j) + 1.0E-7D); ++ double d1 = flag4 ? doublelist.getDouble(i++) : doublelist1.getDouble(j++); ++ ++ if ((i != 0 && flag2 || flag4 || flag1) && (j != 0 && flag3 || !flag4 || flag)) { ++ if (d0 < d1 - 1.0E-7D) { ++ this.b.add(i - 1); ++ this.c.add(j - 1); ++ this.a.add(d1); ++ d0 = d1; ++ } else if (!this.a.isEmpty()) { ++ this.b.set(this.b.size() - 1, i - 1); ++ this.c.set(this.c.size() - 1, j - 1); ++ } ++ } ++ } ++ } ++ ++ @Override ++ public boolean a(VoxelShapeMerger.a voxelshapemerger_a) { ++ for (int i = 0; i < this.a.size() - 1; ++i) { ++ if (!voxelshapemerger_a.merge(this.b.getInt(i), this.c.getInt(i), i)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ @Override ++ public DoubleList a() { ++ return this.a; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +new file mode 100644 +index 0000000000000000000000000000000000000000..44d37272a337fee9606ebaa1b6f647c0fd392320 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +@@ -0,0 +1,346 @@ ++package net.minecraft.world.phys.shapes; ++ ++import com.google.common.annotations.VisibleForTesting; ++import com.google.common.math.DoubleMath; ++import com.google.common.math.IntMath; ++import it.unimi.dsi.fastutil.doubles.DoubleArrayList; ++import it.unimi.dsi.fastutil.doubles.DoubleList; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.Objects; ++import java.util.stream.Stream; ++import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumAxisCycle; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.util.MathHelper; ++import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.IWorldReader; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.phys.AxisAlignedBB; ++ ++public final class VoxelShapes { ++ ++ private static final VoxelShape b = (VoxelShape) SystemUtils.a(() -> { ++ VoxelShapeBitSet voxelshapebitset = new VoxelShapeBitSet(1, 1, 1); ++ ++ voxelshapebitset.a(0, 0, 0, true, true); ++ return new VoxelShapeCube(voxelshapebitset); ++ }); ++ public static final VoxelShape a = create(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); ++ private static final VoxelShape c = new VoxelShapeArray(new VoxelShapeBitSet(0, 0, 0), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D})); ++ ++ public static VoxelShape a() { ++ return VoxelShapes.c; ++ } ++ ++ public static VoxelShape b() { ++ return VoxelShapes.b; ++ } ++ ++ public static VoxelShape create(double d0, double d1, double d2, double d3, double d4, double d5) { ++ return a(new AxisAlignedBB(d0, d1, d2, d3, d4, d5)); ++ } ++ ++ public static VoxelShape a(AxisAlignedBB axisalignedbb) { ++ int i = a(axisalignedbb.minX, axisalignedbb.maxX); ++ int j = a(axisalignedbb.minY, axisalignedbb.maxY); ++ int k = a(axisalignedbb.minZ, axisalignedbb.maxZ); ++ ++ if (i >= 0 && j >= 0 && k >= 0) { ++ if (i == 0 && j == 0 && k == 0) { ++ return axisalignedbb.e(0.5D, 0.5D, 0.5D) ? b() : a(); ++ } else { ++ int l = 1 << i; ++ int i1 = 1 << j; ++ int j1 = 1 << k; ++ int k1 = (int) Math.round(axisalignedbb.minX * (double) l); ++ int l1 = (int) Math.round(axisalignedbb.maxX * (double) l); ++ int i2 = (int) Math.round(axisalignedbb.minY * (double) i1); ++ int j2 = (int) Math.round(axisalignedbb.maxY * (double) i1); ++ int k2 = (int) Math.round(axisalignedbb.minZ * (double) j1); ++ int l2 = (int) Math.round(axisalignedbb.maxZ * (double) j1); ++ VoxelShapeBitSet voxelshapebitset = new VoxelShapeBitSet(l, i1, j1, k1, i2, k2, l1, j2, l2); ++ ++ for (long i3 = (long) k1; i3 < (long) l1; ++i3) { ++ for (long j3 = (long) i2; j3 < (long) j2; ++j3) { ++ for (long k3 = (long) k2; k3 < (long) l2; ++k3) { ++ voxelshapebitset.a((int) i3, (int) j3, (int) k3, false, true); ++ } ++ } ++ } ++ ++ return new VoxelShapeCube(voxelshapebitset); ++ } ++ } else { ++ return new VoxelShapeArray(VoxelShapes.b.a, new double[]{axisalignedbb.minX, axisalignedbb.maxX}, new double[]{axisalignedbb.minY, axisalignedbb.maxY}, new double[]{axisalignedbb.minZ, axisalignedbb.maxZ}); ++ } ++ } ++ ++ private static int a(double d0, double d1) { ++ if (d0 >= -1.0E-7D && d1 <= 1.0000001D) { ++ for (int i = 0; i <= 3; ++i) { ++ double d2 = d0 * (double) (1 << i); ++ double d3 = d1 * (double) (1 << i); ++ boolean flag = Math.abs(d2 - Math.floor(d2)) < 1.0E-7D; ++ boolean flag1 = Math.abs(d3 - Math.floor(d3)) < 1.0E-7D; ++ ++ if (flag && flag1) { ++ return i; ++ } ++ } ++ ++ return -1; ++ } else { ++ return -1; ++ } ++ } ++ ++ protected static long a(int i, int j) { ++ return (long) i * (long) (j / IntMath.gcd(i, j)); ++ } ++ ++ public static VoxelShape a(VoxelShape voxelshape, VoxelShape voxelshape1) { ++ return a(voxelshape, voxelshape1, OperatorBoolean.OR); ++ } ++ ++ public static VoxelShape a(VoxelShape voxelshape, VoxelShape... avoxelshape) { ++ return (VoxelShape) Arrays.stream(avoxelshape).reduce(voxelshape, VoxelShapes::a); ++ } ++ ++ public static VoxelShape a(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { ++ return b(voxelshape, voxelshape1, operatorboolean).c(); ++ } ++ ++ public static VoxelShape b(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { ++ if (operatorboolean.apply(false, false)) { ++ throw (IllegalArgumentException) SystemUtils.c((Throwable) (new IllegalArgumentException())); ++ } else if (voxelshape == voxelshape1) { ++ return operatorboolean.apply(true, true) ? voxelshape : a(); ++ } else { ++ boolean flag = operatorboolean.apply(true, false); ++ boolean flag1 = operatorboolean.apply(false, true); ++ ++ if (voxelshape.isEmpty()) { ++ return flag1 ? voxelshape1 : a(); ++ } else if (voxelshape1.isEmpty()) { ++ return flag ? voxelshape : a(); ++ } else { ++ VoxelShapeMerger voxelshapemerger = a(1, voxelshape.a(EnumDirection.EnumAxis.X), voxelshape1.a(EnumDirection.EnumAxis.X), flag, flag1); ++ VoxelShapeMerger voxelshapemerger1 = a(voxelshapemerger.a().size() - 1, voxelshape.a(EnumDirection.EnumAxis.Y), voxelshape1.a(EnumDirection.EnumAxis.Y), flag, flag1); ++ VoxelShapeMerger voxelshapemerger2 = a((voxelshapemerger.a().size() - 1) * (voxelshapemerger1.a().size() - 1), voxelshape.a(EnumDirection.EnumAxis.Z), voxelshape1.a(EnumDirection.EnumAxis.Z), flag, flag1); ++ VoxelShapeBitSet voxelshapebitset = VoxelShapeBitSet.a(voxelshape.a, voxelshape1.a, voxelshapemerger, voxelshapemerger1, voxelshapemerger2, operatorboolean); ++ ++ return (VoxelShape) (voxelshapemerger instanceof VoxelShapeCubeMerger && voxelshapemerger1 instanceof VoxelShapeCubeMerger && voxelshapemerger2 instanceof VoxelShapeCubeMerger ? new VoxelShapeCube(voxelshapebitset) : new VoxelShapeArray(voxelshapebitset, voxelshapemerger.a(), voxelshapemerger1.a(), voxelshapemerger2.a())); ++ } ++ } ++ } ++ ++ public static boolean c(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { ++ if (operatorboolean.apply(false, false)) { ++ throw (IllegalArgumentException) SystemUtils.c((Throwable) (new IllegalArgumentException())); ++ } else if (voxelshape == voxelshape1) { ++ return operatorboolean.apply(true, true); ++ } else if (voxelshape.isEmpty()) { ++ return operatorboolean.apply(false, !voxelshape1.isEmpty()); ++ } else if (voxelshape1.isEmpty()) { ++ return operatorboolean.apply(!voxelshape.isEmpty(), false); ++ } else { ++ boolean flag = operatorboolean.apply(true, false); ++ boolean flag1 = operatorboolean.apply(false, true); ++ EnumDirection.EnumAxis[] aenumdirection_enumaxis = EnumAxisCycle.d; ++ int i = aenumdirection_enumaxis.length; ++ ++ for (int j = 0; j < i; ++j) { ++ EnumDirection.EnumAxis enumdirection_enumaxis = aenumdirection_enumaxis[j]; ++ ++ if (voxelshape.c(enumdirection_enumaxis) < voxelshape1.b(enumdirection_enumaxis) - 1.0E-7D) { ++ return flag || flag1; ++ } ++ ++ if (voxelshape1.c(enumdirection_enumaxis) < voxelshape.b(enumdirection_enumaxis) - 1.0E-7D) { ++ return flag || flag1; ++ } ++ } ++ ++ VoxelShapeMerger voxelshapemerger = a(1, voxelshape.a(EnumDirection.EnumAxis.X), voxelshape1.a(EnumDirection.EnumAxis.X), flag, flag1); ++ VoxelShapeMerger voxelshapemerger1 = a(voxelshapemerger.a().size() - 1, voxelshape.a(EnumDirection.EnumAxis.Y), voxelshape1.a(EnumDirection.EnumAxis.Y), flag, flag1); ++ VoxelShapeMerger voxelshapemerger2 = a((voxelshapemerger.a().size() - 1) * (voxelshapemerger1.a().size() - 1), voxelshape.a(EnumDirection.EnumAxis.Z), voxelshape1.a(EnumDirection.EnumAxis.Z), flag, flag1); ++ ++ return a(voxelshapemerger, voxelshapemerger1, voxelshapemerger2, voxelshape.a, voxelshape1.a, operatorboolean); ++ } ++ } ++ ++ private static boolean a(VoxelShapeMerger voxelshapemerger, VoxelShapeMerger voxelshapemerger1, VoxelShapeMerger voxelshapemerger2, VoxelShapeDiscrete voxelshapediscrete, VoxelShapeDiscrete voxelshapediscrete1, OperatorBoolean operatorboolean) { ++ return !voxelshapemerger.a((i, j, k) -> { ++ return voxelshapemerger1.a((l, i1, j1) -> { ++ return voxelshapemerger2.a((k1, l1, i2) -> { ++ return !operatorboolean.apply(voxelshapediscrete.c(i, l, k1), voxelshapediscrete1.c(j, i1, l1)); ++ }); ++ }); ++ }); ++ } ++ ++ public static double a(EnumDirection.EnumAxis enumdirection_enumaxis, AxisAlignedBB axisalignedbb, Stream stream, double d0) { ++ for (Iterator iterator = stream.iterator(); iterator.hasNext(); d0 = ((VoxelShape) iterator.next()).a(enumdirection_enumaxis, axisalignedbb, d0)) { ++ if (Math.abs(d0) < 1.0E-7D) { ++ return 0.0D; ++ } ++ } ++ ++ return d0; ++ } ++ ++ public static double a(EnumDirection.EnumAxis enumdirection_enumaxis, AxisAlignedBB axisalignedbb, IWorldReader iworldreader, double d0, VoxelShapeCollision voxelshapecollision, Stream stream) { ++ return a(axisalignedbb, iworldreader, d0, voxelshapecollision, EnumAxisCycle.a(enumdirection_enumaxis, EnumDirection.EnumAxis.Z), stream); ++ } ++ ++ private static double a(AxisAlignedBB axisalignedbb, IWorldReader iworldreader, double d0, VoxelShapeCollision voxelshapecollision, EnumAxisCycle enumaxiscycle, Stream stream) { ++ if (axisalignedbb.b() >= 1.0E-6D && axisalignedbb.c() >= 1.0E-6D && axisalignedbb.d() >= 1.0E-6D) { ++ if (Math.abs(d0) < 1.0E-7D) { ++ return 0.0D; ++ } else { ++ EnumAxisCycle enumaxiscycle1 = enumaxiscycle.a(); ++ EnumDirection.EnumAxis enumdirection_enumaxis = enumaxiscycle1.a(EnumDirection.EnumAxis.X); ++ EnumDirection.EnumAxis enumdirection_enumaxis1 = enumaxiscycle1.a(EnumDirection.EnumAxis.Y); ++ EnumDirection.EnumAxis enumdirection_enumaxis2 = enumaxiscycle1.a(EnumDirection.EnumAxis.Z); ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); ++ int i = MathHelper.floor(axisalignedbb.a(enumdirection_enumaxis) - 1.0E-7D) - 1; ++ int j = MathHelper.floor(axisalignedbb.b(enumdirection_enumaxis) + 1.0E-7D) + 1; ++ int k = MathHelper.floor(axisalignedbb.a(enumdirection_enumaxis1) - 1.0E-7D) - 1; ++ int l = MathHelper.floor(axisalignedbb.b(enumdirection_enumaxis1) + 1.0E-7D) + 1; ++ double d1 = axisalignedbb.a(enumdirection_enumaxis2) - 1.0E-7D; ++ double d2 = axisalignedbb.b(enumdirection_enumaxis2) + 1.0E-7D; ++ boolean flag = d0 > 0.0D; ++ int i1 = flag ? MathHelper.floor(axisalignedbb.b(enumdirection_enumaxis2) - 1.0E-7D) - 1 : MathHelper.floor(axisalignedbb.a(enumdirection_enumaxis2) + 1.0E-7D) + 1; ++ int j1 = a(d0, d1, d2); ++ int k1 = flag ? 1 : -1; ++ int l1 = i1; ++ ++ while (true) { ++ if (flag) { ++ if (l1 > j1) { ++ break; ++ } ++ } else if (l1 < j1) { ++ break; ++ } ++ ++ for (int i2 = i; i2 <= j; ++i2) { ++ for (int j2 = k; j2 <= l; ++j2) { ++ int k2 = 0; ++ ++ if (i2 == i || i2 == j) { ++ ++k2; ++ } ++ ++ if (j2 == k || j2 == l) { ++ ++k2; ++ } ++ ++ if (l1 == i1 || l1 == j1) { ++ ++k2; ++ } ++ ++ if (k2 < 3) { ++ blockposition_mutableblockposition.a(enumaxiscycle1, i2, j2, l1); ++ IBlockData iblockdata = iworldreader.getType(blockposition_mutableblockposition); ++ ++ if ((k2 != 1 || iblockdata.d()) && (k2 != 2 || iblockdata.a(Blocks.MOVING_PISTON))) { ++ d0 = iblockdata.b((IBlockAccess) iworldreader, blockposition_mutableblockposition, voxelshapecollision).a(enumdirection_enumaxis2, axisalignedbb.d((double) (-blockposition_mutableblockposition.getX()), (double) (-blockposition_mutableblockposition.getY()), (double) (-blockposition_mutableblockposition.getZ())), d0); ++ if (Math.abs(d0) < 1.0E-7D) { ++ return 0.0D; ++ } ++ ++ j1 = a(d0, d1, d2); ++ } ++ } ++ } ++ } ++ ++ l1 += k1; ++ } ++ ++ double[] adouble = new double[]{d0}; ++ ++ stream.forEach((voxelshape) -> { ++ adouble[0] = voxelshape.a(enumdirection_enumaxis2, axisalignedbb, adouble[0]); ++ }); ++ return adouble[0]; ++ } ++ } else { ++ return d0; ++ } ++ } ++ ++ private static int a(double d0, double d1, double d2) { ++ return d0 > 0.0D ? MathHelper.floor(d2 + d0) + 1 : MathHelper.floor(d1 + d0) - 1; ++ } ++ ++ public static VoxelShape a(VoxelShape voxelshape, EnumDirection enumdirection) { ++ if (voxelshape == b()) { ++ return b(); ++ } else { ++ EnumDirection.EnumAxis enumdirection_enumaxis = enumdirection.n(); ++ boolean flag; ++ int i; ++ ++ if (enumdirection.e() == EnumDirection.EnumAxisDirection.POSITIVE) { ++ flag = DoubleMath.fuzzyEquals(voxelshape.c(enumdirection_enumaxis), 1.0D, 1.0E-7D); ++ i = voxelshape.a.c(enumdirection_enumaxis) - 1; ++ } else { ++ flag = DoubleMath.fuzzyEquals(voxelshape.b(enumdirection_enumaxis), 0.0D, 1.0E-7D); ++ i = 0; ++ } ++ ++ return (VoxelShape) (!flag ? a() : new VoxelShapeSlice(voxelshape, enumdirection_enumaxis, i)); ++ } ++ } ++ ++ public static boolean b(VoxelShape voxelshape, VoxelShape voxelshape1, EnumDirection enumdirection) { ++ if (voxelshape != b() && voxelshape1 != b()) { ++ EnumDirection.EnumAxis enumdirection_enumaxis = enumdirection.n(); ++ EnumDirection.EnumAxisDirection enumdirection_enumaxisdirection = enumdirection.e(); ++ VoxelShape voxelshape2 = enumdirection_enumaxisdirection == EnumDirection.EnumAxisDirection.POSITIVE ? voxelshape : voxelshape1; ++ VoxelShape voxelshape3 = enumdirection_enumaxisdirection == EnumDirection.EnumAxisDirection.POSITIVE ? voxelshape1 : voxelshape; ++ ++ if (!DoubleMath.fuzzyEquals(voxelshape2.c(enumdirection_enumaxis), 1.0D, 1.0E-7D)) { ++ voxelshape2 = a(); ++ } ++ ++ if (!DoubleMath.fuzzyEquals(voxelshape3.b(enumdirection_enumaxis), 0.0D, 1.0E-7D)) { ++ voxelshape3 = a(); ++ } ++ ++ return !c(b(), b(new VoxelShapeSlice(voxelshape2, enumdirection_enumaxis, voxelshape2.a.c(enumdirection_enumaxis) - 1), new VoxelShapeSlice(voxelshape3, enumdirection_enumaxis, 0), OperatorBoolean.OR), OperatorBoolean.ONLY_FIRST); ++ } else { ++ return true; ++ } ++ } ++ ++ public static boolean b(VoxelShape voxelshape, VoxelShape voxelshape1) { ++ return voxelshape != b() && voxelshape1 != b() ? (voxelshape.isEmpty() && voxelshape1.isEmpty() ? false : !c(b(), b(voxelshape, voxelshape1, OperatorBoolean.OR), OperatorBoolean.ONLY_FIRST)) : true; ++ } ++ ++ @VisibleForTesting ++ protected static VoxelShapeMerger a(int i, DoubleList doublelist, DoubleList doublelist1, boolean flag, boolean flag1) { ++ int j = doublelist.size() - 1; ++ int k = doublelist1.size() - 1; ++ ++ if (doublelist instanceof VoxelShapeCubePoint && doublelist1 instanceof VoxelShapeCubePoint) { ++ long l = a(j, k); ++ ++ if ((long) i * l <= 256L) { ++ return new VoxelShapeCubeMerger(j, k); ++ } ++ } ++ ++ return (VoxelShapeMerger) (doublelist.getDouble(j) < doublelist1.getDouble(0) - 1.0E-7D ? new VoxelShapeMergerDisjoint(doublelist, doublelist1, false) : (doublelist1.getDouble(k) < doublelist.getDouble(0) - 1.0E-7D ? new VoxelShapeMergerDisjoint(doublelist1, doublelist, true) : (j == k && Objects.equals(doublelist, doublelist1) ? (doublelist instanceof VoxelShapeMergerIdentical ? (VoxelShapeMerger) doublelist : (doublelist1 instanceof VoxelShapeMergerIdentical ? (VoxelShapeMerger) doublelist1 : new VoxelShapeMergerIdentical(doublelist))) : new VoxelShapeMergerList(doublelist, doublelist1, flag, flag1)))); ++ } ++ ++ public interface a { ++ ++ void consume(double d0, double d1, double d2, double d3, double d4, double d5); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/scores/PersistentScoreboard.java b/src/main/java/net/minecraft/world/scores/PersistentScoreboard.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3998565ccd87c966c0fb9e6757cd1861faa5bc15 +--- /dev/null ++++ b/src/main/java/net/minecraft/world/scores/PersistentScoreboard.java +@@ -0,0 +1,256 @@ ++package net.minecraft.world.scores; ++ ++import java.util.Collection; ++import java.util.Iterator; ++import net.minecraft.EnumChatFormat; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.nbt.NBTTagString; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.network.chat.IChatMutableComponent; ++import net.minecraft.world.level.saveddata.PersistentBase; ++import net.minecraft.world.scores.criteria.IScoreboardCriteria; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++public class PersistentScoreboard extends PersistentBase { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ private Scoreboard b; ++ private NBTTagCompound c; ++ ++ public PersistentScoreboard() { ++ super("scoreboard"); ++ } ++ ++ public void a(Scoreboard scoreboard) { ++ this.b = scoreboard; ++ if (this.c != null) { ++ this.a(this.c); ++ } ++ ++ } ++ ++ @Override ++ public void a(NBTTagCompound nbttagcompound) { ++ if (this.b == null) { ++ this.c = nbttagcompound; ++ } else { ++ this.b(nbttagcompound.getList("Objectives", 10)); ++ this.b.a(nbttagcompound.getList("PlayerScores", 10)); ++ if (nbttagcompound.hasKeyOfType("DisplaySlots", 10)) { ++ this.c(nbttagcompound.getCompound("DisplaySlots")); ++ } ++ ++ if (nbttagcompound.hasKeyOfType("Teams", 9)) { ++ this.a(nbttagcompound.getList("Teams", 10)); ++ } ++ ++ } ++ } ++ ++ protected void a(NBTTagList nbttaglist) { ++ for (int i = 0; i < nbttaglist.size(); ++i) { ++ NBTTagCompound nbttagcompound = nbttaglist.getCompound(i); ++ String s = nbttagcompound.getString("Name"); ++ ++ if (s.length() > 16) { ++ s = s.substring(0, 16); ++ } ++ ++ ScoreboardTeam scoreboardteam = this.b.createTeam(s); ++ IChatMutableComponent ichatmutablecomponent = IChatBaseComponent.ChatSerializer.a(nbttagcompound.getString("DisplayName")); ++ ++ if (ichatmutablecomponent != null) { ++ scoreboardteam.setDisplayName(ichatmutablecomponent); ++ } ++ ++ if (nbttagcompound.hasKeyOfType("TeamColor", 8)) { ++ scoreboardteam.setColor(EnumChatFormat.b(nbttagcompound.getString("TeamColor"))); ++ } ++ ++ if (nbttagcompound.hasKeyOfType("AllowFriendlyFire", 99)) { ++ scoreboardteam.setAllowFriendlyFire(nbttagcompound.getBoolean("AllowFriendlyFire")); ++ } ++ ++ if (nbttagcompound.hasKeyOfType("SeeFriendlyInvisibles", 99)) { ++ scoreboardteam.setCanSeeFriendlyInvisibles(nbttagcompound.getBoolean("SeeFriendlyInvisibles")); ++ } ++ ++ IChatMutableComponent ichatmutablecomponent1; ++ ++ if (nbttagcompound.hasKeyOfType("MemberNamePrefix", 8)) { ++ ichatmutablecomponent1 = IChatBaseComponent.ChatSerializer.a(nbttagcompound.getString("MemberNamePrefix")); ++ if (ichatmutablecomponent1 != null) { ++ scoreboardteam.setPrefix(ichatmutablecomponent1); ++ } ++ } ++ ++ if (nbttagcompound.hasKeyOfType("MemberNameSuffix", 8)) { ++ ichatmutablecomponent1 = IChatBaseComponent.ChatSerializer.a(nbttagcompound.getString("MemberNameSuffix")); ++ if (ichatmutablecomponent1 != null) { ++ scoreboardteam.setSuffix(ichatmutablecomponent1); ++ } ++ } ++ ++ ScoreboardTeamBase.EnumNameTagVisibility scoreboardteambase_enumnametagvisibility; ++ ++ if (nbttagcompound.hasKeyOfType("NameTagVisibility", 8)) { ++ scoreboardteambase_enumnametagvisibility = ScoreboardTeamBase.EnumNameTagVisibility.a(nbttagcompound.getString("NameTagVisibility")); ++ if (scoreboardteambase_enumnametagvisibility != null) { ++ scoreboardteam.setNameTagVisibility(scoreboardteambase_enumnametagvisibility); ++ } ++ } ++ ++ if (nbttagcompound.hasKeyOfType("DeathMessageVisibility", 8)) { ++ scoreboardteambase_enumnametagvisibility = ScoreboardTeamBase.EnumNameTagVisibility.a(nbttagcompound.getString("DeathMessageVisibility")); ++ if (scoreboardteambase_enumnametagvisibility != null) { ++ scoreboardteam.setDeathMessageVisibility(scoreboardteambase_enumnametagvisibility); ++ } ++ } ++ ++ if (nbttagcompound.hasKeyOfType("CollisionRule", 8)) { ++ ScoreboardTeamBase.EnumTeamPush scoreboardteambase_enumteampush = ScoreboardTeamBase.EnumTeamPush.a(nbttagcompound.getString("CollisionRule")); ++ ++ if (scoreboardteambase_enumteampush != null) { ++ scoreboardteam.setCollisionRule(scoreboardteambase_enumteampush); ++ } ++ } ++ ++ this.a(scoreboardteam, nbttagcompound.getList("Players", 8)); ++ } ++ ++ } ++ ++ protected void a(ScoreboardTeam scoreboardteam, NBTTagList nbttaglist) { ++ for (int i = 0; i < nbttaglist.size(); ++i) { ++ this.b.addPlayerToTeam(nbttaglist.getString(i), scoreboardteam); ++ } ++ ++ } ++ ++ protected void c(NBTTagCompound nbttagcompound) { ++ for (int i = 0; i < 19; ++i) { ++ if (nbttagcompound.hasKeyOfType("slot_" + i, 8)) { ++ String s = nbttagcompound.getString("slot_" + i); ++ ScoreboardObjective scoreboardobjective = this.b.getObjective(s); ++ ++ this.b.setDisplaySlot(i, scoreboardobjective); ++ } ++ } ++ ++ } ++ ++ protected void b(NBTTagList nbttaglist) { ++ for (int i = 0; i < nbttaglist.size(); ++i) { ++ NBTTagCompound nbttagcompound = nbttaglist.getCompound(i); ++ ++ IScoreboardCriteria.a(nbttagcompound.getString("CriteriaName")).ifPresent((iscoreboardcriteria) -> { ++ String s = nbttagcompound.getString("Name"); ++ ++ if (s.length() > 16) { ++ s = s.substring(0, 16); ++ } ++ ++ IChatMutableComponent ichatmutablecomponent = IChatBaseComponent.ChatSerializer.a(nbttagcompound.getString("DisplayName")); ++ IScoreboardCriteria.EnumScoreboardHealthDisplay iscoreboardcriteria_enumscoreboardhealthdisplay = IScoreboardCriteria.EnumScoreboardHealthDisplay.a(nbttagcompound.getString("RenderType")); ++ ++ this.b.registerObjective(s, iscoreboardcriteria, ichatmutablecomponent, iscoreboardcriteria_enumscoreboardhealthdisplay); ++ }); ++ } ++ ++ } ++ ++ @Override ++ public NBTTagCompound b(NBTTagCompound nbttagcompound) { ++ if (this.b == null) { ++ PersistentScoreboard.LOGGER.warn("Tried to save scoreboard without having a scoreboard..."); ++ return nbttagcompound; ++ } else { ++ nbttagcompound.set("Objectives", this.e()); ++ nbttagcompound.set("PlayerScores", this.b.i()); ++ nbttagcompound.set("Teams", this.a()); ++ this.d(nbttagcompound); ++ return nbttagcompound; ++ } ++ } ++ ++ protected NBTTagList a() { ++ NBTTagList nbttaglist = new NBTTagList(); ++ Collection collection = this.b.getTeams(); ++ Iterator iterator = collection.iterator(); ++ ++ while (iterator.hasNext()) { ++ ScoreboardTeam scoreboardteam = (ScoreboardTeam) iterator.next(); ++ NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ ++ nbttagcompound.setString("Name", scoreboardteam.getName()); ++ nbttagcompound.setString("DisplayName", IChatBaseComponent.ChatSerializer.a(scoreboardteam.getDisplayName())); ++ if (scoreboardteam.getColor().b() >= 0) { ++ nbttagcompound.setString("TeamColor", scoreboardteam.getColor().f()); ++ } ++ ++ nbttagcompound.setBoolean("AllowFriendlyFire", scoreboardteam.allowFriendlyFire()); ++ nbttagcompound.setBoolean("SeeFriendlyInvisibles", scoreboardteam.canSeeFriendlyInvisibles()); ++ nbttagcompound.setString("MemberNamePrefix", IChatBaseComponent.ChatSerializer.a(scoreboardteam.getPrefix())); ++ nbttagcompound.setString("MemberNameSuffix", IChatBaseComponent.ChatSerializer.a(scoreboardteam.getSuffix())); ++ nbttagcompound.setString("NameTagVisibility", scoreboardteam.getNameTagVisibility().e); ++ nbttagcompound.setString("DeathMessageVisibility", scoreboardteam.getDeathMessageVisibility().e); ++ nbttagcompound.setString("CollisionRule", scoreboardteam.getCollisionRule().e); ++ NBTTagList nbttaglist1 = new NBTTagList(); ++ Iterator iterator1 = scoreboardteam.getPlayerNameSet().iterator(); ++ ++ while (iterator1.hasNext()) { ++ String s = (String) iterator1.next(); ++ ++ nbttaglist1.add(NBTTagString.a(s)); ++ } ++ ++ nbttagcompound.set("Players", nbttaglist1); ++ nbttaglist.add(nbttagcompound); ++ } ++ ++ return nbttaglist; ++ } ++ ++ protected void d(NBTTagCompound nbttagcompound) { ++ NBTTagCompound nbttagcompound1 = new NBTTagCompound(); ++ boolean flag = false; ++ ++ for (int i = 0; i < 19; ++i) { ++ ScoreboardObjective scoreboardobjective = this.b.getObjectiveForSlot(i); ++ ++ if (scoreboardobjective != null) { ++ nbttagcompound1.setString("slot_" + i, scoreboardobjective.getName()); ++ flag = true; ++ } ++ } ++ ++ if (flag) { ++ nbttagcompound.set("DisplaySlots", nbttagcompound1); ++ } ++ ++ } ++ ++ protected NBTTagList e() { ++ NBTTagList nbttaglist = new NBTTagList(); ++ Collection collection = this.b.getObjectives(); ++ Iterator iterator = collection.iterator(); ++ ++ while (iterator.hasNext()) { ++ ScoreboardObjective scoreboardobjective = (ScoreboardObjective) iterator.next(); ++ ++ if (scoreboardobjective.getCriteria() != null) { ++ NBTTagCompound nbttagcompound = new NBTTagCompound(); ++ ++ nbttagcompound.setString("Name", scoreboardobjective.getName()); ++ nbttagcompound.setString("CriteriaName", scoreboardobjective.getCriteria().getName()); ++ nbttagcompound.setString("DisplayName", IChatBaseComponent.ChatSerializer.a(scoreboardobjective.getDisplayName())); ++ nbttagcompound.setString("RenderType", scoreboardobjective.getRenderType().a()); ++ nbttaglist.add(nbttagcompound); ++ } ++ } ++ ++ return nbttaglist; ++ } ++} diff --git a/patches/server-unmapped/0001/0002-POM-Changes.patch b/patches/server-unmapped/0001/0002-POM-Changes.patch new file mode 100644 index 0000000000..bbf3917f3b --- /dev/null +++ b/patches/server-unmapped/0001/0002-POM-Changes.patch @@ -0,0 +1,275 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 29 Feb 2016 20:40:33 -0600 +Subject: [PATCH] POM Changes + + +diff --git a/pom.xml b/pom.xml +index ebce3da9abf550089ead322bc2cef359c803a434..5710dd02c80bc713e5dd289c9aa4bc21fd0f4318 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -1,15 +1,14 @@ + + 4.0.0 +- org.spigotmc +- spigot ++ paper + jar + 1.16.5-R0.1-SNAPSHOT +- Spigot +- https://www.spigotmc.org/ ++ Paper ++ https://papermc.io + + +- true ++ + UTF-8 + unknown + git +@@ -20,21 +19,39 @@ + + + +- org.spigotmc +- spigot-parent ++ com.destroystokyo.paper ++ paper-parent + dev-SNAPSHOT + ../pom.xml + + ++ ++ ++ ++ org.apache.logging.log4j ++ log4j-bom ++ 2.11.2 ++ pom ++ import ++ ++ ++ ++ + + +- org.spigotmc +- spigot-api ++ com.destroystokyo.paper ++ paper-api + ${project.version} + compile + + +- org.spigotmc ++ com.destroystokyo.paper ++ paper-mojangapi ++ ${project.version} ++ compile ++ ++ ++ io.papermc + minecraft-server + ${minecraft.version}-SNAPSHOT + compile +@@ -45,18 +62,15 @@ + 2.12.1 + compile + ++ ++ org.apache.logging.log4j ++ log4j-api ++ compile ++ + + org.apache.logging.log4j + log4j-iostreams +- 2.8.1 + compile +- +- +- +- org.apache.logging.log4j +- log4j-api +- +- + + + org.ow2.asm +@@ -64,6 +78,17 @@ + 9.1 + compile + ++ ++ ++ co.aikar ++ cleaner ++ 1.0-SNAPSHOT ++ ++ ++ io.netty ++ netty-all ++ 4.1.50.Final ++ + + + com.googlecode.json-simple +@@ -80,7 +105,7 @@ + + mysql + mysql-connector-java +- 5.1.49 ++ 8.0.23 + runtime + + +@@ -100,34 +125,22 @@ + + + ++ paper-${minecraft.version} ++ clean install + + +- net.md-5 +- scriptus +- 0.4.1 ++ com.lukegb.mojo ++ gitdescribe-maven-plugin ++ 1.3 ++ ++ git-Paper- ++ .. ++ + + +- ex-spigot +- +- ${bt.name}-Spigot-%s +- ../ +- spigot.desc +- +- initialize ++ compile + +- describe +- +- +- +- ex-craftbukkit +- +- -%s +- ../../CraftBukkit +- craftbukkit.desc +- +- initialize +- +- describe ++ gitdescribe + + + +@@ -137,6 +150,7 @@ + maven-jar-plugin + 3.2.0 + ++ true + + + false +@@ -144,11 +158,13 @@ + + org.bukkit.craftbukkit.Main + CraftBukkit +- ${spigot.desc}${craftbukkit.desc} +- ${project.build.outputTimestamp} ++ ++ ${describe} ++ ${maven.build.timestamp} + Bukkit + ${api.version} + Bukkit Team ++ true + + + +@@ -184,14 +200,24 @@ + shade + + ++ ${project.build.directory}/dependency-reduced-pom.xml + ${shadeSourcesJar} + + +- org.spigotmc:minecraft-server ++ io.papermc:minecraft-server + + com/google/common/** + com/google/gson/** + com/google/thirdparty/** ++ ++ io/netty/** ++ META-INF/native/libnetty* ++ com/mojang/brigadier/** ++ META-INF/MANIFEST.MF ++ com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.class ++ com/mojang/datafixers/util/Either* ++ org/apache/logging/log4j/** ++ META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat + + + +@@ -207,10 +233,11 @@ + jline + org.bukkit.craftbukkit.libs.jline + +- +- it.unimi +- org.bukkit.craftbukkit.libs.it.unimi +- ++ ++ ++ ++ ++ + + org.apache.commons.codec + org.bukkit.craftbukkit.libs.org.apache.commons.codec +@@ -258,10 +285,6 @@ + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 +- +- +- eclipse +- + + + org.codehaus.plexus +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 036c870d991118e5c09eced8485b94579bc6782a..c6417a0594ffe2d3650ec54d44e575e347a1f724 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -186,7 +186,7 @@ public class Main { + } + + if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { +- Date buildDate = new Date(Integer.parseInt(Main.class.getPackage().getImplementationVendor()) * 1000L); ++ Date buildDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(Main.class.getPackage().getImplementationVendor()); // Paper + + Calendar deadline = Calendar.getInstance(); + deadline.add(Calendar.DAY_OF_YEAR, -28); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +index 93046379d0cefd5d3236fc59e698809acdc18f80..674096cab190d62622f9947853b056f57d43a2a5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +@@ -11,7 +11,7 @@ public final class Versioning { + public static String getBukkitVersion() { + String result = "Unknown-Version"; + +- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.spigotmc/spigot-api/pom.properties"); ++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/com.destroystokyo.paper/paper-api/pom.properties"); + Properties properties = new Properties(); + + if (stream != null) { diff --git a/patches/server-unmapped/0001/0003-Paper-config-files.patch b/patches/server-unmapped/0001/0003-Paper-config-files.patch new file mode 100644 index 0000000000..c882f4b151 --- /dev/null +++ b/patches/server-unmapped/0001/0003-Paper-config-files.patch @@ -0,0 +1,820 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 29 Feb 2016 21:02:09 -0600 +Subject: [PATCH] Paper config files + +Loads each yml file for early init too so it can be used for early options + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..68cd4134cb6a00c1768100462f8e9e94f3fa6279 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -0,0 +1,286 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.Functions; ++import com.google.common.base.Joiner; ++import com.google.common.collect.ImmutableSet; ++import com.google.common.collect.Iterables; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkProviderServer; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import org.apache.commons.lang3.tuple.MutablePair; ++import org.apache.commons.lang3.tuple.Pair; ++import org.bukkit.Bukkit; ++import org.bukkit.ChatColor; ++import org.bukkit.Location; ++import org.bukkit.World; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.entity.Player; ++ ++import java.io.File; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Set; ++import java.util.stream.Collectors; ++ ++public class PaperCommand extends Command { ++ private static final String BASE_PERM = "bukkit.command.paper."; ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version").build(); ++ ++ public PaperCommand(String name) { ++ super(name); ++ this.description = "Paper related commands"; ++ this.usageMessage = "/paper [" + Joiner.on(" | ").join(SUBCOMMANDS) + "]"; ++ this.setPermission("bukkit.command.paper;" + Joiner.on(';').join(SUBCOMMANDS.stream().map(s -> BASE_PERM + s).collect(Collectors.toSet()))); ++ } ++ ++ private static boolean testPermission(CommandSender commandSender, String permission) { ++ if (commandSender.hasPermission(BASE_PERM + permission) || commandSender.hasPermission("bukkit.command.paper")) return true; ++ commandSender.sendMessage(Bukkit.getPermissionMessage()); ++ return false; ++ } ++ ++ @Override ++ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { ++ if (args.length <= 1) ++ return getListMatchingLast(sender, args, SUBCOMMANDS); ++ ++ switch (args[0].toLowerCase(Locale.ENGLISH)) ++ { ++ case "entity": ++ if (args.length == 2) ++ return getListMatchingLast(sender, args, "help", "list"); ++ if (args.length == 3) ++ return getListMatchingLast(sender, args, EntityTypes.getEntityNameList().stream().map(MinecraftKey::toString).sorted().toArray(String[]::new)); ++ break; ++ } ++ return Collections.emptyList(); ++ } ++ ++ // Code from Mojang - copyright them ++ public static List getListMatchingLast(CommandSender sender, String[] args, String... matches) { ++ return getListMatchingLast(sender, args, (Collection) Arrays.asList(matches)); ++ } ++ ++ public static boolean matches(String s, String s1) { ++ return s1.regionMatches(true, 0, s, 0, s.length()); ++ } ++ ++ public static List getListMatchingLast(CommandSender sender, String[] strings, Collection collection) { ++ String last = strings[strings.length - 1]; ++ ArrayList results = Lists.newArrayList(); ++ ++ if (!collection.isEmpty()) { ++ Iterator iterator = Iterables.transform(collection, Functions.toStringFunction()).iterator(); ++ ++ while (iterator.hasNext()) { ++ String s1 = (String) iterator.next(); ++ ++ if (matches(last, s1) && (sender.hasPermission(BASE_PERM + s1) || sender.hasPermission("bukkit.command.paper"))) { ++ results.add(s1); ++ } ++ } ++ ++ if (results.isEmpty()) { ++ iterator = collection.iterator(); ++ ++ while (iterator.hasNext()) { ++ Object object = iterator.next(); ++ ++ if (object instanceof MinecraftKey && matches(last, ((MinecraftKey) object).getKey())) { ++ results.add(String.valueOf(object)); ++ } ++ } ++ } ++ } ++ ++ return results; ++ } ++ // end copy stuff ++ ++ @Override ++ public boolean execute(CommandSender sender, String commandLabel, String[] args) { ++ if (!testPermission(sender)) return true; ++ ++ if (args.length == 0) { ++ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); ++ return false; ++ } ++ if (SUBCOMMANDS.contains(args[0].toLowerCase(Locale.ENGLISH))) { ++ if (!testPermission(sender, args[0].toLowerCase(Locale.ENGLISH))) return true; ++ } ++ switch (args[0].toLowerCase(Locale.ENGLISH)) { ++ case "heap": ++ dumpHeap(sender); ++ break; ++ case "entity": ++ listEntities(sender, args); ++ break; ++ case "reload": ++ doReload(sender); ++ break; ++ case "ver": ++ if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) ++ case "version": ++ Command ver = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version"); ++ if (ver != null) { ++ ver.execute(sender, commandLabel, new String[0]); ++ break; ++ } ++ // else - fall through to default ++ default: ++ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); ++ return false; ++ } ++ ++ return true; ++ } ++ ++ /* ++ * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 ++ */ ++ private void listEntities(CommandSender sender, String[] args) { ++ if (args.length < 2 || args[1].toLowerCase(Locale.ENGLISH).equals("help")) { ++ sender.sendMessage(ChatColor.RED + "Use /paper entity [list] help for more information on a specific command."); ++ return; ++ } ++ ++ switch (args[1].toLowerCase(Locale.ENGLISH)) { ++ case "list": ++ String filter = "*"; ++ if (args.length > 2) { ++ if (args[2].toLowerCase(Locale.ENGLISH).equals("help")) { ++ sender.sendMessage(ChatColor.RED + "Use /paper entity list [filter] [worldName] to get entity info that matches the optional filter."); ++ return; ++ } ++ filter = args[2]; ++ } ++ final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); ++ Set names = EntityTypes.getEntityNameList().stream() ++ .filter(n -> n.toString().matches(cleanfilter)) ++ .collect(Collectors.toSet()); ++ ++ if (names.isEmpty()) { ++ sender.sendMessage(ChatColor.RED + "Invalid filter, does not match any entities. Use /paper entity list for a proper list"); ++ sender.sendMessage(ChatColor.RED + "Usage: /paper entity list [filter] [worldName]"); ++ return; ++ } ++ ++ String worldName; ++ if (args.length > 3) { ++ worldName = args[3]; ++ } else if (sender instanceof Player) { ++ worldName = ((Player) sender).getWorld().getName(); ++ } else { ++ sender.sendMessage(ChatColor.RED + "Please specify the name of a world"); ++ sender.sendMessage(ChatColor.RED + "To do so without a filter, specify '*' as the filter"); ++ sender.sendMessage(ChatColor.RED + "Usage: /paper entity list [filter] [worldName]"); ++ return; ++ } ++ ++ Map>> list = Maps.newHashMap(); ++ World bukkitWorld = Bukkit.getWorld(worldName); ++ if (bukkitWorld == null) { ++ sender.sendMessage(ChatColor.RED + "Could not load world for " + worldName + ". Please select a valid world."); ++ sender.sendMessage(ChatColor.RED + "Usage: /paper entity list [filter] [worldName]"); ++ return; ++ } ++ WorldServer world = ((CraftWorld) Bukkit.getWorld(worldName)).getHandle(); ++ ++ Map nonEntityTicking = Maps.newHashMap(); ++ ChunkProviderServer chunkProviderServer = world.getChunkProvider(); ++ ++ Collection entities = world.entitiesById.values(); ++ entities.forEach(e -> { ++ MinecraftKey key = new MinecraftKey(""); // TODO: update in next patch ++ ++ MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); ++ ChunkCoordIntPair chunk = new ChunkCoordIntPair(e.chunkX, e.chunkZ); ++ info.left++; ++ info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); ++ if (!chunkProviderServer.isInEntityTickingChunk(e)) { ++ nonEntityTicking.merge(key, Integer.valueOf(1), Integer::sum); ++ } ++ }); ++ ++ if (names.size() == 1) { ++ MinecraftKey name = names.iterator().next(); ++ Pair> info = list.get(name); ++ int nonTicking = nonEntityTicking.getOrDefault(name, Integer.valueOf(0)).intValue(); ++ if (info == null) { ++ sender.sendMessage(ChatColor.RED + "No entities found."); ++ return; ++ } ++ sender.sendMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking); ++ info.getRight().entrySet().stream() ++ .sorted((a, b) -> !a.getValue().equals(b.getValue()) ? b.getValue() - a.getValue() : a.getKey().toString().compareTo(b.getKey().toString())) ++ .limit(10).forEach(e -> sender.sendMessage(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isEntityTickingChunk(e.getKey()) ? " (Ticking)" : " (Non-Ticking)"))); ++ } else { ++ List> info = list.entrySet().stream() ++ .filter(e -> names.contains(e.getKey())) ++ .map(e -> Pair.of(e.getKey(), e.getValue().left)) ++ .sorted((a, b) -> !a.getRight().equals(b.getRight()) ? b.getRight() - a.getRight() : a.getKey().toString().compareTo(b.getKey().toString())) ++ .collect(Collectors.toList()); ++ ++ if (info == null || info.size() == 0) { ++ sender.sendMessage(ChatColor.RED + "No entities found."); ++ return; ++ } ++ ++ int count = info.stream().mapToInt(Pair::getRight).sum(); ++ int nonTickingCount = nonEntityTicking.values().stream().mapToInt(Integer::intValue).sum(); ++ sender.sendMessage("Total Ticking: " + (count - nonTickingCount) + ", Total Non-Ticking: " + nonTickingCount); ++ info.forEach(e -> { ++ int nonTicking = nonEntityTicking.getOrDefault(e.getKey(), Integer.valueOf(0)).intValue(); ++ sender.sendMessage(" " + (e.getValue() - nonTicking) + " (" + nonTicking + ") " + ": " + e.getKey()); ++ }); ++ sender.sendMessage("* First number is ticking entities, second number is non-ticking entities"); ++ } ++ break; ++ } ++ } ++ ++ private void dumpHeap(CommandSender sender) { ++ java.nio.file.Path dir = java.nio.file.Paths.get("./dumps"); ++ String name = "heap-dump-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()); ++ ++ Command.broadcastCommandMessage(sender, ChatColor.YELLOW + "Writing JVM heap data..."); ++ ++ java.nio.file.Path file = CraftServer.dumpHeap(dir, name); ++ if (file != null) { ++ Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Heap dump saved to " + file); ++ } else { ++ Command.broadcastCommandMessage(sender, ChatColor.RED + "Failed to write heap dump, see sever log for details"); ++ } ++ } ++ ++ private void doReload(CommandSender sender) { ++ Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues."); ++ Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); ++ ++ MinecraftServer console = MinecraftServer.getServer(); ++ com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); ++ for (WorldServer world : console.getWorlds()) { ++ world.paperConfig.init(); ++ } ++ console.server.reloadCount++; ++ ++ Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Paper config reload complete."); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2c0514892d3993bef57ecf677cf8bb0fbe0216e4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -0,0 +1,185 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.Throwables; ++ ++import java.io.File; ++import java.io.IOException; ++import java.lang.reflect.InvocationTargetException; ++import java.lang.reflect.Method; ++import java.lang.reflect.Modifier; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.TimeUnit; ++import java.util.logging.Level; ++import java.util.regex.Pattern; ++ ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Bukkit; ++import org.bukkit.command.Command; ++import org.bukkit.configuration.ConfigurationSection; ++import org.bukkit.configuration.InvalidConfigurationException; ++import org.bukkit.configuration.file.YamlConfiguration; ++ ++public class PaperConfig { ++ ++ private static File CONFIG_FILE; ++ private static final String HEADER = "This is the main configuration file for Paper.\n" ++ + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n" ++ + "with caution, and make sure you know what each option does before configuring.\n" ++ + "\n" ++ + "If you need help with the configuration or have any questions related to Paper,\n" ++ + "join us in our Discord or IRC channel.\n" ++ + "\n" ++ + "Discord: https://discord.gg/papermc\n" ++ + "IRC: #paper @ irc.esper.net ( https://webchat.esper.net/?channels=paper ) \n" ++ + "Website: https://papermc.io/ \n" ++ + "Docs: https://paper.readthedocs.org/ \n"; ++ /*========================================================================*/ ++ public static YamlConfiguration config; ++ static int version; ++ static Map commands; ++ private static boolean verbose; ++ private static boolean fatalError; ++ /*========================================================================*/ ++ ++ public static void init(File configFile) { ++ CONFIG_FILE = configFile; ++ config = new YamlConfiguration(); ++ try { ++ config.load(CONFIG_FILE); ++ } catch (IOException ex) { ++ } catch (InvalidConfigurationException ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Could not load paper.yml, please correct your syntax errors", ex); ++ throw Throwables.propagate(ex); ++ } ++ config.options().header(HEADER); ++ config.options().copyDefaults(true); ++ verbose = getBoolean("verbose", false); ++ ++ commands = new HashMap(); ++ commands.put("paper", new PaperCommand("paper")); ++ ++ version = getInt("config-version", 20); ++ set("config-version", 20); ++ readConfig(PaperConfig.class, null); ++ } ++ ++ protected static void logError(String s) { ++ Bukkit.getLogger().severe(s); ++ } ++ ++ protected static void fatal(String s) { ++ fatalError = true; ++ throw new RuntimeException("Fatal paper.yml config error: " + s); ++ } ++ ++ protected static void log(String s) { ++ if (verbose) { ++ Bukkit.getLogger().info(s); ++ } ++ } ++ ++ public static void registerCommands() { ++ for (Map.Entry entry : commands.entrySet()) { ++ MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Paper", entry.getValue()); ++ } ++ } ++ ++ static void readConfig(Class clazz, Object instance) { ++ for (Method method : clazz.getDeclaredMethods()) { ++ if (Modifier.isPrivate(method.getModifiers())) { ++ if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { ++ try { ++ method.setAccessible(true); ++ method.invoke(instance); ++ } catch (InvocationTargetException ex) { ++ throw Throwables.propagate(ex.getCause()); ++ } catch (Exception ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); ++ } ++ } ++ } ++ } ++ ++ try { ++ config.save(CONFIG_FILE); ++ } catch (IOException ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex); ++ } ++ } ++ ++ private static final Pattern SPACE = Pattern.compile(" "); ++ private static final Pattern NOT_NUMERIC = Pattern.compile("[^-\\d.]"); ++ public static int getSeconds(String str) { ++ str = SPACE.matcher(str).replaceAll(""); ++ final char unit = str.charAt(str.length() - 1); ++ str = NOT_NUMERIC.matcher(str).replaceAll(""); ++ double num; ++ try { ++ num = Double.parseDouble(str); ++ } catch (Exception e) { ++ num = 0D; ++ } ++ switch (unit) { ++ case 'd': num *= (double) 60*60*24; break; ++ case 'h': num *= (double) 60*60; break; ++ case 'm': num *= (double) 60; break; ++ default: case 's': break; ++ } ++ return (int) num; ++ } ++ ++ protected static String timeSummary(int seconds) { ++ String time = ""; ++ ++ if (seconds > 60 * 60 * 24) { ++ time += TimeUnit.SECONDS.toDays(seconds) + "d"; ++ seconds %= 60 * 60 * 24; ++ } ++ ++ if (seconds > 60 * 60) { ++ time += TimeUnit.SECONDS.toHours(seconds) + "h"; ++ seconds %= 60 * 60; ++ } ++ ++ if (seconds > 0) { ++ time += TimeUnit.SECONDS.toMinutes(seconds) + "m"; ++ } ++ return time; ++ } ++ ++ private static void set(String path, Object val) { ++ config.set(path, val); ++ } ++ ++ private static boolean getBoolean(String path, boolean def) { ++ config.addDefault(path, def); ++ return config.getBoolean(path, config.getBoolean(path)); ++ } ++ ++ private static double getDouble(String path, double def) { ++ config.addDefault(path, def); ++ return config.getDouble(path, config.getDouble(path)); ++ } ++ ++ private static float getFloat(String path, float def) { ++ // TODO: Figure out why getFloat() always returns the default value. ++ return (float) getDouble(path, (double) def); ++ } ++ ++ private static int getInt(String path, int def) { ++ config.addDefault(path, def); ++ return config.getInt(path, config.getInt(path)); ++ } ++ ++ private static List getList(String path, T def) { ++ config.addDefault(path, def); ++ return (List) config.getList(path, config.getList(path)); ++ } ++ ++ private static String getString(String path, String def) { ++ config.addDefault(path, def); ++ return config.getString(path, config.getString(path)); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b31109d2dadd29e8852468c19265066b773d2be0 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -0,0 +1,68 @@ ++package com.destroystokyo.paper; ++ ++import java.util.List; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.configuration.file.YamlConfiguration; ++import org.spigotmc.SpigotWorldConfig; ++ ++import static com.destroystokyo.paper.PaperConfig.log; ++import static com.destroystokyo.paper.PaperConfig.logError; ++ ++public class PaperWorldConfig { ++ ++ private final String worldName; ++ private final SpigotWorldConfig spigotConfig; ++ private YamlConfiguration config; ++ private boolean verbose; ++ ++ public PaperWorldConfig(String worldName, SpigotWorldConfig spigotConfig) { ++ this.worldName = worldName; ++ this.spigotConfig = spigotConfig; ++ this.config = PaperConfig.config; ++ init(); ++ } ++ ++ public void init() { ++ this.config = PaperConfig.config; // grab updated reference ++ log("-------- World Settings For [" + worldName + "] --------"); ++ PaperConfig.readConfig(PaperWorldConfig.class, this); ++ } ++ ++ private void set(String path, Object val) { ++ config.set("world-settings.default." + path, val); ++ if (config.get("world-settings." + worldName + "." + path) != null) { ++ config.set("world-settings." + worldName + "." + path, val); ++ } ++ } ++ ++ private boolean getBoolean(String path, boolean def) { ++ config.addDefault("world-settings.default." + path, def); ++ return config.getBoolean("world-settings." + worldName + "." + path, config.getBoolean("world-settings.default." + path)); ++ } ++ ++ private double getDouble(String path, double def) { ++ config.addDefault("world-settings.default." + path, def); ++ return config.getDouble("world-settings." + worldName + "." + path, config.getDouble("world-settings.default." + path)); ++ } ++ ++ private int getInt(String path, int def) { ++ config.addDefault("world-settings.default." + path, def); ++ return config.getInt("world-settings." + worldName + "." + path, config.getInt("world-settings.default." + path)); ++ } ++ ++ private float getFloat(String path, float def) { ++ // TODO: Figure out why getFloat() always returns the default value. ++ return (float) getDouble(path, (double) def); ++ } ++ ++ private List getList(String path, List def) { ++ config.addDefault("world-settings.default." + path, def); ++ return (List) config.getList("world-settings." + worldName + "." + path, config.getList("world-settings.default." + path)); ++ } ++ ++ private String getString(String path, String def) { ++ config.addDefault("world-settings.default." + path, def); ++ return config.getString("world-settings." + worldName + "." + path, config.getString("world-settings.default." + path)); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index b746e30e6fc5ea0c17465b510d737316f1ab7004..89db31061fcc3420bc8e668533a4051cdbd12253 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -103,6 +103,12 @@ public class Main { + DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(iregistrycustom_dimension, optionset); // CraftBukkit - CLI argument support + + dedicatedserversettings.save(); ++ // Paper start - load config files for access below if needed ++ org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = loadConfigFile((File) optionset.valueOf("bukkit-settings")); ++ org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = loadConfigFile((File) optionset.valueOf("spigot-settings")); ++ org.bukkit.configuration.file.YamlConfiguration paperConfiguration = loadConfigFile((File) optionset.valueOf("paper-settings")); ++ // Paper end ++ + java.nio.file.Path java_nio_file_path1 = Paths.get("eula.txt"); + EULA eula = new EULA(java_nio_file_path1); + +@@ -244,6 +250,20 @@ public class Main { + + } + ++ // Paper start - load config files ++ private static org.bukkit.configuration.file.YamlConfiguration loadConfigFile(File configFile) throws Exception { ++ org.bukkit.configuration.file.YamlConfiguration config = new org.bukkit.configuration.file.YamlConfiguration(); ++ if (configFile.exists()) { ++ try { ++ config.load(configFile); ++ } catch (Exception ex) { ++ throw new Exception("Failed to load configuration file: " + configFile.getName(), ex); ++ } ++ } ++ return config; ++ } ++ // Paper end ++ + public static void convertWorld(Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, boolean flag, BooleanSupplier booleansupplier, ImmutableSet> immutableset) { // CraftBukkit + Main.LOGGER.info("Forcing world upgrade! {}", convertable_conversionsession.getLevelName()); // CraftBukkit + WorldUpgrader worldupgrader = new WorldUpgrader(convertable_conversionsession, datafixer, immutableset, flag); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index d5628a0197125506f7aaeb89bab7bd8a811b3ad5..8e2e415a022ccd486465f19d9bbf1f287b21fb95 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -187,6 +187,15 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + org.spigotmc.SpigotConfig.init((java.io.File) options.valueOf("spigot-settings")); + org.spigotmc.SpigotConfig.registerCommands(); + // Spigot end ++ // Paper start ++ try { ++ com.destroystokyo.paper.PaperConfig.init((java.io.File) options.valueOf("paper-settings")); ++ } catch (Exception e) { ++ DedicatedServer.LOGGER.error("Unable to load server configuration", e); ++ return false; ++ } ++ com.destroystokyo.paper.PaperConfig.registerCommands(); ++ // Paper end + + this.setPVP(dedicatedserverproperties.pvp); + this.setAllowFlight(dedicatedserverproperties.allowFlight); +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index 7bb513cf82211a22b5f657b0a174ec44ce0dce20..87da50db4de3bd94f50f4a33ab87fc4fd6aaf5e3 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -307,15 +307,15 @@ public class ChunkProviderServer extends IChunkProvider { + } + } + +- @Override +- public boolean a(Entity entity) { ++ public final boolean isInEntityTickingChunk(Entity entity) { return this.a(entity); } // Paper - OBFHELPER ++ @Override public boolean a(Entity entity) { + long i = ChunkCoordIntPair.pair(MathHelper.floor(entity.locX()) >> 4, MathHelper.floor(entity.locZ()) >> 4); + + return this.a(i, (Function>>) PlayerChunk::b); // CraftBukkit - decompile error + } + +- @Override +- public boolean a(ChunkCoordIntPair chunkcoordintpair) { ++ public final boolean isEntityTickingChunk(ChunkCoordIntPair chunkcoordintpair) { return this.a(chunkcoordintpair); } // Paper - OBFHELPER ++ @Override public boolean a(ChunkCoordIntPair chunkcoordintpair) { + return this.a(chunkcoordintpair.pair(), (Function>>) PlayerChunk::b); // CraftBukkit - decompile error + } + +diff --git a/src/main/java/net/minecraft/world/entity/EntityTypes.java b/src/main/java/net/minecraft/world/entity/EntityTypes.java +index 4b0b782ab19a6cea4412b5e9d8c0524aee1402e4..a32bc63ff1960bdb874d546ee42633063834da24 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityTypes.java ++++ b/src/main/java/net/minecraft/world/entity/EntityTypes.java +@@ -2,6 +2,7 @@ package net.minecraft.world.entity; + + import com.google.common.collect.ImmutableSet; + import java.util.Optional; ++import java.util.Set; // Paper + import java.util.UUID; + import java.util.function.Function; + import java.util.stream.Stream; +@@ -599,4 +600,10 @@ public class EntityTypes { + return new EntityTypes<>(this.a, this.b, this.d, this.e, this.f, this.g, this.c, this.j, this.h, this.i); + } + } ++ ++ // Paper start ++ public static Set getEntityNameList() { ++ return IRegistry.ENTITY_TYPE.keySet(); ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 5d1e21e6a63cf6fe5ab9ca53303b690be69b96b1..323149594e47ee4c999befe89210f2dc1e3de860 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -129,6 +129,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public boolean populating; + public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot + ++ public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper ++ + public final SpigotTimings.WorldTimingsHandler timings; // Spigot + public static BlockPosition lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; +@@ -149,6 +151,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + protected World(WorldDataMutable worlddatamutable, ResourceKey resourcekey, final DimensionManager dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) { + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.WorldDataServer) worlddatamutable).getName()); // Spigot ++ this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.WorldDataServer) worlddatamutable).getName(), this.spigotConfig); // Paper + this.generator = gen; + this.world = new CraftWorld((WorldServer) this, gen, env); + this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 20a5e136a8967311026111b4c246c200596cd019..bd62288b6951c6b5962497a3c93989cab5d66ad4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -808,6 +808,7 @@ public final class CraftServer implements Server { + } + + org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot ++ com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper + for (WorldServer world : console.getWorlds()) { + world.worldDataServer.setDifficulty(config.difficulty); + world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals); +@@ -841,6 +842,7 @@ public final class CraftServer implements Server { + world.ticksPerAmbientSpawns = this.getTicksPerAmbientSpawns(); + } + world.spigotConfig.init(); // Spigot ++ world.paperConfig.init(); // Paper + } + + pluginManager.clearPlugins(); +@@ -848,6 +850,7 @@ public final class CraftServer implements Server { + resetRecipes(); + reloadData(); + org.spigotmc.SpigotConfig.registerCommands(); // Spigot ++ com.destroystokyo.paper.PaperConfig.registerCommands(); // Paper + overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*"); + ignoreVanillaPermissions = commandsConfiguration.getBoolean("ignore-vanilla-permissions"); + +@@ -2097,4 +2100,35 @@ public final class CraftServer implements Server { + return spigot; + } + // Spigot end ++ ++ // Paper start ++ @SuppressWarnings({"rawtypes", "unchecked"}) ++ public static java.nio.file.Path dumpHeap(java.nio.file.Path dir, String name) { ++ try { ++ java.nio.file.Files.createDirectories(dir); ++ ++ javax.management.MBeanServer server = java.lang.management.ManagementFactory.getPlatformMBeanServer(); ++ java.nio.file.Path file; ++ ++ try { ++ Class clazz = Class.forName("openj9.lang.management.OpenJ9DiagnosticsMXBean"); ++ Object openj9Mbean = java.lang.management.ManagementFactory.newPlatformMXBeanProxy(server, "openj9.lang.management:type=OpenJ9Diagnostics", clazz); ++ java.lang.reflect.Method m = clazz.getMethod("triggerDumpToFile", String.class, String.class); ++ file = dir.resolve(name + ".phd"); ++ m.invoke(openj9Mbean, "heap", file.toString()); ++ } catch (ClassNotFoundException e) { ++ Class clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean"); ++ Object hotspotMBean = java.lang.management.ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", clazz); ++ java.lang.reflect.Method m = clazz.getMethod("dumpHeap", String.class, boolean.class); ++ file = dir.resolve(name + ".hprof"); ++ m.invoke(hotspotMBean, file.toString(), true); ++ } ++ ++ return file; ++ } catch (Throwable t) { ++ Bukkit.getLogger().log(Level.SEVERE, "Could not write heap", t); ++ return null; ++ } ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index c6417a0594ffe2d3650ec54d44e575e347a1f724..3b48799d084f14722f815cb35cbd48b618380fca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -129,6 +129,14 @@ public class Main { + .defaultsTo(new File("spigot.yml")) + .describedAs("Yml file"); + // Spigot End ++ ++ // Paper Start ++ acceptsAll(asList("paper", "paper-settings"), "File for paper settings") ++ .withRequiredArg() ++ .ofType(File.class) ++ .defaultsTo(new File("paper.yml")) ++ .describedAs("Yml file"); ++ // Paper end + } + }; + +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 83d83ff7ceffbb77723da721b869dfd0091e496d..0efcbab8f8806aeb8dd8bd6384e5a7cee375d100 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -39,36 +39,36 @@ public class SpigotWorldConfig + config.set( "world-settings.default." + path, val ); + } + +- private boolean getBoolean(String path, boolean def) ++ public boolean getBoolean(String path, boolean def) // Paper - private -> public + { + config.addDefault( "world-settings.default." + path, def ); + return config.getBoolean( "world-settings." + worldName + "." + path, config.getBoolean( "world-settings.default." + path ) ); + } + +- private double getDouble(String path, double def) ++ public double getDouble(String path, double def) // Paper - private -> public + { + config.addDefault( "world-settings.default." + path, def ); + return config.getDouble( "world-settings." + worldName + "." + path, config.getDouble( "world-settings.default." + path ) ); + } + +- private int getInt(String path) ++ public int getInt(String path) // Paper - private -> public + { + return config.getInt( "world-settings." + worldName + "." + path ); + } + +- private int getInt(String path, int def) ++ public int getInt(String path, int def) // Paper - private -> public + { + config.addDefault( "world-settings.default." + path, def ); + return config.getInt( "world-settings." + worldName + "." + path, config.getInt( "world-settings.default." + path ) ); + } + +- private List getList(String path, T def) ++ public List getList(String path, T def) // Paper - private -> public + { + config.addDefault( "world-settings.default." + path, def ); + return (List) config.getList( "world-settings." + worldName + "." + path, config.getList( "world-settings.default." + path ) ); + } + +- private String getString(String path, String def) ++ public String getString(String path, String def) // Paper - private -> public + { + config.addDefault( "world-settings.default." + path, def ); + return config.getString( "world-settings." + worldName + "." + path, config.getString( "world-settings.default." + path ) ); diff --git a/patches/server-unmapped/0001/0004-MC-Dev-fixes.patch b/patches/server-unmapped/0001/0004-MC-Dev-fixes.patch new file mode 100644 index 0000000000..855354134e --- /dev/null +++ b/patches/server-unmapped/0001/0004-MC-Dev-fixes.patch @@ -0,0 +1,893 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 30 Mar 2016 19:36:20 -0400 +Subject: [PATCH] MC Dev fixes + + +diff --git a/src/main/java/net/minecraft/SystemUtils.java b/src/main/java/net/minecraft/SystemUtils.java +index 14b1a51b9b675aa175c32990402551fa43ec1599..bc7757b929ecce998094ddcdf51a4703e165a6d6 100644 +--- a/src/main/java/net/minecraft/SystemUtils.java ++++ b/src/main/java/net/minecraft/SystemUtils.java +@@ -65,8 +65,8 @@ public class SystemUtils { + return Collectors.toMap(Entry::getKey, Entry::getValue); + } + +- public static > String a(IBlockState iblockstate, Object object) { +- return iblockstate.a((Comparable) object); ++ public static > String a(IBlockState iblockstate, T object) { // Paper - decompile fix ++ return iblockstate.a(object); // Paper - decompile fix + } + + public static String a(String s, @Nullable MinecraftKey minecraftkey) { +@@ -234,8 +234,8 @@ public class SystemUtils { + public static T b(Iterable iterable, @Nullable T t0) { + Iterator iterator = iterable.iterator(); + +- Object object; +- Object object1; ++ T object; // Paper - decompile fix ++ T object1; // Paper - decompile fix + + for (object1 = null; iterator.hasNext(); object1 = object) { + object = iterator.next(); +@@ -260,7 +260,7 @@ public class SystemUtils { + } + + public static Strategy k() { +- return SystemUtils.IdentityHashingStrategy.INSTANCE; ++ return (Strategy) SystemUtils.IdentityHashingStrategy.INSTANCE; // Paper - decompile fix + } + + public static CompletableFuture> b(List> list) { +@@ -271,7 +271,7 @@ public class SystemUtils { + list.forEach((completablefuture1) -> { + int i = list1.size(); + +- list1.add((Object) null); ++ list1.add(null); // Paper - decompile fix + acompletablefuture[i] = completablefuture1.whenComplete((object, throwable) -> { + if (throwable != null) { + completablefuture.completeExceptionally(throwable); +diff --git a/src/main/java/net/minecraft/core/BlockPosition.java b/src/main/java/net/minecraft/core/BlockPosition.java +index f85889a232998520761731a17f3d293d3360fe2c..76675ad1633dbaebb180842b9914fac18741c62e 100644 +--- a/src/main/java/net/minecraft/core/BlockPosition.java ++++ b/src/main/java/net/minecraft/core/BlockPosition.java +@@ -241,8 +241,8 @@ public class BlockPosition extends BaseBlockPosition { + }; + } + +- public static Iterable a(BlockPosition blockposition, int i, int j, int k) { +- int l = i + j + k; ++ public static Iterable a(BlockPosition blockposition, int p_i, int p_j, int p_k) { // Paper - decompile issues - variable name conflicts to inner class field refs ++ int l_decompiled = p_i + p_j + p_k; // Paper - decompile issues + int i1 = blockposition.getX(); + int j1 = blockposition.getY(); + int k1 = blockposition.getZ(); +@@ -270,15 +270,15 @@ public class BlockPosition extends BaseBlockPosition { + ++this.l; + if (this.l > this.j) { + ++this.i; +- if (this.i > l) { ++ if (this.i > l_decompiled) { // Paper - use proper l above (first line of this method) + return (BlockPosition) this.endOfData(); + } + +- this.j = Math.min(i, this.i); ++ this.j = Math.min(p_i, this.i); // Paper - decompile issues + this.l = -this.j; + } + +- this.k = Math.min(j, this.i - Math.abs(this.l)); ++ this.k = Math.min(p_j, this.i - Math.abs(this.l)); // Paper - decompile issues + this.m = -this.k; + } + +@@ -286,7 +286,7 @@ public class BlockPosition extends BaseBlockPosition { + int i2 = this.m; + int j2 = this.i - Math.abs(l1) - Math.abs(i2); + +- if (j2 <= k) { ++ if (j2 <= p_k) { // Paper - decompile issues + this.n = j2 != 0; + blockposition_mutableblockposition = this.h.d(i1 + l1, j1 + i2, k1 + j2); + } +@@ -355,13 +355,13 @@ public class BlockPosition extends BaseBlockPosition { + }; + } + +- public static Iterable a(BlockPosition blockposition, int i, EnumDirection enumdirection, EnumDirection enumdirection1) { ++ public static Iterable a(BlockPosition blockposition, int I, EnumDirection enumdirection, EnumDirection enumdirection1) { // Paper - decompile fix + Validate.validState(enumdirection.n() != enumdirection1.n(), "The two directions cannot be on the same axis", new Object[0]); + return () -> { + return new AbstractIterator() { + private final EnumDirection[] e = new EnumDirection[]{enumdirection, enumdirection1, enumdirection.opposite(), enumdirection1.opposite()}; + private final BlockPosition.MutableBlockPosition f = blockposition.i().c(enumdirection1); +- private final int g = 4 * i; ++ private final int g = 4 * I; + private int h = -1; + private int i; + private int j; +diff --git a/src/main/java/net/minecraft/core/RegistryBlockID.java b/src/main/java/net/minecraft/core/RegistryBlockID.java +index b173ed8a6abeee41ce48e03f6403f2eb4978155b..e543b6927280a14e1d1220534758289934e31282 100644 +--- a/src/main/java/net/minecraft/core/RegistryBlockID.java ++++ b/src/main/java/net/minecraft/core/RegistryBlockID.java +@@ -27,7 +27,7 @@ public class RegistryBlockID implements Registry { + this.b.put(t0, i); + + while (this.c.size() <= i) { +- this.c.add((Object) null); ++ this.c.add(null); // Paper - decompile fix + } + + this.c.set(i, t0); +@@ -41,6 +41,13 @@ public class RegistryBlockID implements Registry { + this.a(t0, this.a); + } + ++ // Paper start - decompile fix ++ @Override ++ public int a(T t) { ++ return getId(t); ++ } ++ // Paper end ++ + public int getId(T t0) { + Integer integer = (Integer) this.b.get(t0); + +diff --git a/src/main/java/net/minecraft/nbt/NBTBase.java b/src/main/java/net/minecraft/nbt/NBTBase.java +index 170a65cb13e7b87f64cd28331431ba55d53702cd..d6e51f82f6df2d7058806f3e483766e18398af77 100644 +--- a/src/main/java/net/minecraft/nbt/NBTBase.java ++++ b/src/main/java/net/minecraft/nbt/NBTBase.java +@@ -20,7 +20,7 @@ public interface NBTBase { + + NBTTagType b(); + +- NBTBase clone(); ++ public NBTBase clone(); // Paper - decompile fix + + default String asString() { + return this.toString(); +diff --git a/src/main/java/net/minecraft/nbt/NBTCompressedStreamTools.java b/src/main/java/net/minecraft/nbt/NBTCompressedStreamTools.java +index be4d54099a0deb0e0275208ac61c8a172a48b398..850d3a7bb8ae4c43c0e2f737cfe69261f338b026 100644 +--- a/src/main/java/net/minecraft/nbt/NBTCompressedStreamTools.java ++++ b/src/main/java/net/minecraft/nbt/NBTCompressedStreamTools.java +@@ -18,6 +18,7 @@ import java.util.zip.GZIPOutputStream; + import net.minecraft.CrashReport; + import net.minecraft.CrashReportSystemDetails; + import net.minecraft.ReportedException; ++import io.netty.buffer.ByteBufInputStream; // Paper + + public class NBTCompressedStreamTools { + +@@ -137,7 +138,7 @@ public class NBTCompressedStreamTools { + + public static NBTTagCompound a(DataInput datainput, NBTReadLimiter nbtreadlimiter) throws IOException { + // Spigot start +- if ( datainput instanceof io.netty.buffer.ByteBufInputStream ) ++ if ( datainput instanceof ByteBufInputStream) // Paper + { + datainput = new DataInputStream(new org.spigotmc.LimitStream((InputStream) datainput, nbtreadlimiter)); + } +diff --git a/src/main/java/net/minecraft/nbt/NBTTagList.java b/src/main/java/net/minecraft/nbt/NBTTagList.java +index 35cca76fb7c7aa736e64185b44016e65cfaef6cd..4f6f6f51f9807bafa88482c0fe776c8b163107d7 100644 +--- a/src/main/java/net/minecraft/nbt/NBTTagList.java ++++ b/src/main/java/net/minecraft/nbt/NBTTagList.java +@@ -53,7 +53,7 @@ public class NBTTagList extends NBTList { + return "TAG_List"; + } + }; +- private static final ByteSet b = new ByteOpenHashSet(Arrays.asList(1, 2, 3, 4, 5, 6)); ++ private static final ByteSet b = new ByteOpenHashSet(Arrays.asList((byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6)); // Paper - decompiler fix + private final List list; + private byte type; + +diff --git a/src/main/java/net/minecraft/network/EnumProtocol.java b/src/main/java/net/minecraft/network/EnumProtocol.java +index ab08336043d4f558434ed1f38d25cc555ace1ac0..a892521db1197369bf6363bd2f5da24bf53643ab 100644 +--- a/src/main/java/net/minecraft/network/EnumProtocol.java ++++ b/src/main/java/net/minecraft/network/EnumProtocol.java +@@ -12,6 +12,8 @@ import javax.annotation.Nullable; + import net.minecraft.SystemUtils; + import net.minecraft.network.protocol.EnumProtocolDirection; + import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.game.PacketListenerPlayIn; ++import net.minecraft.network.protocol.game.PacketListenerPlayOut; + import net.minecraft.network.protocol.game.PacketPlayInAbilities; + import net.minecraft.network.protocol.game.PacketPlayInAdvancements; + import net.minecraft.network.protocol.game.PacketPlayInArmAnimation; +@@ -146,24 +148,30 @@ import net.minecraft.network.protocol.game.PacketPlayOutWindowItems; + import net.minecraft.network.protocol.game.PacketPlayOutWorldBorder; + import net.minecraft.network.protocol.game.PacketPlayOutWorldEvent; + import net.minecraft.network.protocol.game.PacketPlayOutWorldParticles; ++import net.minecraft.network.protocol.handshake.PacketHandshakingInListener; + import net.minecraft.network.protocol.handshake.PacketHandshakingInSetProtocol; + import net.minecraft.network.protocol.login.PacketLoginInCustomPayload; + import net.minecraft.network.protocol.login.PacketLoginInEncryptionBegin; ++import net.minecraft.network.protocol.login.PacketLoginInListener; + import net.minecraft.network.protocol.login.PacketLoginInStart; + import net.minecraft.network.protocol.login.PacketLoginOutCustomPayload; + import net.minecraft.network.protocol.login.PacketLoginOutDisconnect; + import net.minecraft.network.protocol.login.PacketLoginOutEncryptionBegin; ++import net.minecraft.network.protocol.login.PacketLoginOutListener; + import net.minecraft.network.protocol.login.PacketLoginOutSetCompression; + import net.minecraft.network.protocol.login.PacketLoginOutSuccess; ++import net.minecraft.network.protocol.status.PacketStatusInListener; + import net.minecraft.network.protocol.status.PacketStatusInPing; + import net.minecraft.network.protocol.status.PacketStatusInStart; ++import net.minecraft.network.protocol.status.PacketStatusOutListener; + import net.minecraft.network.protocol.status.PacketStatusOutPong; + import net.minecraft.network.protocol.status.PacketStatusOutServerInfo; + import org.apache.logging.log4j.LogManager; + + public enum EnumProtocol { + +- HANDSHAKING(-1, b().a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a<>()).a(PacketHandshakingInSetProtocol.class, PacketHandshakingInSetProtocol::new))), PLAY(0, b().a(EnumProtocolDirection.CLIENTBOUND, (new EnumProtocol.a<>()).a(PacketPlayOutSpawnEntity.class, PacketPlayOutSpawnEntity::new).a(PacketPlayOutSpawnEntityExperienceOrb.class, PacketPlayOutSpawnEntityExperienceOrb::new).a(PacketPlayOutSpawnEntityLiving.class, PacketPlayOutSpawnEntityLiving::new).a(PacketPlayOutSpawnEntityPainting.class, PacketPlayOutSpawnEntityPainting::new).a(PacketPlayOutNamedEntitySpawn.class, PacketPlayOutNamedEntitySpawn::new).a(PacketPlayOutAnimation.class, PacketPlayOutAnimation::new).a(PacketPlayOutStatistic.class, PacketPlayOutStatistic::new).a(PacketPlayOutBlockBreak.class, PacketPlayOutBlockBreak::new).a(PacketPlayOutBlockBreakAnimation.class, PacketPlayOutBlockBreakAnimation::new).a(PacketPlayOutTileEntityData.class, PacketPlayOutTileEntityData::new).a(PacketPlayOutBlockAction.class, PacketPlayOutBlockAction::new).a(PacketPlayOutBlockChange.class, PacketPlayOutBlockChange::new).a(PacketPlayOutBoss.class, PacketPlayOutBoss::new).a(PacketPlayOutServerDifficulty.class, PacketPlayOutServerDifficulty::new).a(PacketPlayOutChat.class, PacketPlayOutChat::new).a(PacketPlayOutTabComplete.class, PacketPlayOutTabComplete::new).a(PacketPlayOutCommands.class, PacketPlayOutCommands::new).a(PacketPlayOutTransaction.class, PacketPlayOutTransaction::new).a(PacketPlayOutCloseWindow.class, PacketPlayOutCloseWindow::new).a(PacketPlayOutWindowItems.class, PacketPlayOutWindowItems::new).a(PacketPlayOutWindowData.class, PacketPlayOutWindowData::new).a(PacketPlayOutSetSlot.class, PacketPlayOutSetSlot::new).a(PacketPlayOutSetCooldown.class, PacketPlayOutSetCooldown::new).a(PacketPlayOutCustomPayload.class, PacketPlayOutCustomPayload::new).a(PacketPlayOutCustomSoundEffect.class, PacketPlayOutCustomSoundEffect::new).a(PacketPlayOutKickDisconnect.class, PacketPlayOutKickDisconnect::new).a(PacketPlayOutEntityStatus.class, PacketPlayOutEntityStatus::new).a(PacketPlayOutExplosion.class, PacketPlayOutExplosion::new).a(PacketPlayOutUnloadChunk.class, PacketPlayOutUnloadChunk::new).a(PacketPlayOutGameStateChange.class, PacketPlayOutGameStateChange::new).a(PacketPlayOutOpenWindowHorse.class, PacketPlayOutOpenWindowHorse::new).a(PacketPlayOutKeepAlive.class, PacketPlayOutKeepAlive::new).a(PacketPlayOutMapChunk.class, PacketPlayOutMapChunk::new).a(PacketPlayOutWorldEvent.class, PacketPlayOutWorldEvent::new).a(PacketPlayOutWorldParticles.class, PacketPlayOutWorldParticles::new).a(PacketPlayOutLightUpdate.class, PacketPlayOutLightUpdate::new).a(PacketPlayOutLogin.class, PacketPlayOutLogin::new).a(PacketPlayOutMap.class, PacketPlayOutMap::new).a(PacketPlayOutOpenWindowMerchant.class, PacketPlayOutOpenWindowMerchant::new).a(PacketPlayOutEntity.PacketPlayOutRelEntityMove.class, PacketPlayOutEntity.PacketPlayOutRelEntityMove::new).a(PacketPlayOutEntity.PacketPlayOutRelEntityMoveLook.class, PacketPlayOutEntity.PacketPlayOutRelEntityMoveLook::new).a(PacketPlayOutEntity.PacketPlayOutEntityLook.class, PacketPlayOutEntity.PacketPlayOutEntityLook::new).a(PacketPlayOutEntity.class, PacketPlayOutEntity::new).a(PacketPlayOutVehicleMove.class, PacketPlayOutVehicleMove::new).a(PacketPlayOutOpenBook.class, PacketPlayOutOpenBook::new).a(PacketPlayOutOpenWindow.class, PacketPlayOutOpenWindow::new).a(PacketPlayOutOpenSignEditor.class, PacketPlayOutOpenSignEditor::new).a(PacketPlayOutAutoRecipe.class, PacketPlayOutAutoRecipe::new).a(PacketPlayOutAbilities.class, PacketPlayOutAbilities::new).a(PacketPlayOutCombatEvent.class, PacketPlayOutCombatEvent::new).a(PacketPlayOutPlayerInfo.class, PacketPlayOutPlayerInfo::new).a(PacketPlayOutLookAt.class, PacketPlayOutLookAt::new).a(PacketPlayOutPosition.class, PacketPlayOutPosition::new).a(PacketPlayOutRecipes.class, PacketPlayOutRecipes::new).a(PacketPlayOutEntityDestroy.class, PacketPlayOutEntityDestroy::new).a(PacketPlayOutRemoveEntityEffect.class, PacketPlayOutRemoveEntityEffect::new).a(PacketPlayOutResourcePackSend.class, PacketPlayOutResourcePackSend::new).a(PacketPlayOutRespawn.class, PacketPlayOutRespawn::new).a(PacketPlayOutEntityHeadRotation.class, PacketPlayOutEntityHeadRotation::new).a(PacketPlayOutMultiBlockChange.class, PacketPlayOutMultiBlockChange::new).a(PacketPlayOutSelectAdvancementTab.class, PacketPlayOutSelectAdvancementTab::new).a(PacketPlayOutWorldBorder.class, PacketPlayOutWorldBorder::new).a(PacketPlayOutCamera.class, PacketPlayOutCamera::new).a(PacketPlayOutHeldItemSlot.class, PacketPlayOutHeldItemSlot::new).a(PacketPlayOutViewCentre.class, PacketPlayOutViewCentre::new).a(PacketPlayOutViewDistance.class, PacketPlayOutViewDistance::new).a(PacketPlayOutSpawnPosition.class, PacketPlayOutSpawnPosition::new).a(PacketPlayOutScoreboardDisplayObjective.class, PacketPlayOutScoreboardDisplayObjective::new).a(PacketPlayOutEntityMetadata.class, PacketPlayOutEntityMetadata::new).a(PacketPlayOutAttachEntity.class, PacketPlayOutAttachEntity::new).a(PacketPlayOutEntityVelocity.class, PacketPlayOutEntityVelocity::new).a(PacketPlayOutEntityEquipment.class, PacketPlayOutEntityEquipment::new).a(PacketPlayOutExperience.class, PacketPlayOutExperience::new).a(PacketPlayOutUpdateHealth.class, PacketPlayOutUpdateHealth::new).a(PacketPlayOutScoreboardObjective.class, PacketPlayOutScoreboardObjective::new).a(PacketPlayOutMount.class, PacketPlayOutMount::new).a(PacketPlayOutScoreboardTeam.class, PacketPlayOutScoreboardTeam::new).a(PacketPlayOutScoreboardScore.class, PacketPlayOutScoreboardScore::new).a(PacketPlayOutUpdateTime.class, PacketPlayOutUpdateTime::new).a(PacketPlayOutTitle.class, PacketPlayOutTitle::new).a(PacketPlayOutEntitySound.class, PacketPlayOutEntitySound::new).a(PacketPlayOutNamedSoundEffect.class, PacketPlayOutNamedSoundEffect::new).a(PacketPlayOutStopSound.class, PacketPlayOutStopSound::new).a(PacketPlayOutPlayerListHeaderFooter.class, PacketPlayOutPlayerListHeaderFooter::new).a(PacketPlayOutNBTQuery.class, PacketPlayOutNBTQuery::new).a(PacketPlayOutCollect.class, PacketPlayOutCollect::new).a(PacketPlayOutEntityTeleport.class, PacketPlayOutEntityTeleport::new).a(PacketPlayOutAdvancements.class, PacketPlayOutAdvancements::new).a(PacketPlayOutUpdateAttributes.class, PacketPlayOutUpdateAttributes::new).a(PacketPlayOutEntityEffect.class, PacketPlayOutEntityEffect::new).a(PacketPlayOutRecipeUpdate.class, PacketPlayOutRecipeUpdate::new).a(PacketPlayOutTags.class, PacketPlayOutTags::new)).a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a<>()).a(PacketPlayInTeleportAccept.class, PacketPlayInTeleportAccept::new).a(PacketPlayInTileNBTQuery.class, PacketPlayInTileNBTQuery::new).a(PacketPlayInDifficultyChange.class, PacketPlayInDifficultyChange::new).a(PacketPlayInChat.class, PacketPlayInChat::new).a(PacketPlayInClientCommand.class, PacketPlayInClientCommand::new).a(PacketPlayInSettings.class, PacketPlayInSettings::new).a(PacketPlayInTabComplete.class, PacketPlayInTabComplete::new).a(PacketPlayInTransaction.class, PacketPlayInTransaction::new).a(PacketPlayInEnchantItem.class, PacketPlayInEnchantItem::new).a(PacketPlayInWindowClick.class, PacketPlayInWindowClick::new).a(PacketPlayInCloseWindow.class, PacketPlayInCloseWindow::new).a(PacketPlayInCustomPayload.class, PacketPlayInCustomPayload::new).a(PacketPlayInBEdit.class, PacketPlayInBEdit::new).a(PacketPlayInEntityNBTQuery.class, PacketPlayInEntityNBTQuery::new).a(PacketPlayInUseEntity.class, PacketPlayInUseEntity::new).a(PacketPlayInJigsawGenerate.class, PacketPlayInJigsawGenerate::new).a(PacketPlayInKeepAlive.class, PacketPlayInKeepAlive::new).a(PacketPlayInDifficultyLock.class, PacketPlayInDifficultyLock::new).a(PacketPlayInFlying.PacketPlayInPosition.class, PacketPlayInFlying.PacketPlayInPosition::new).a(PacketPlayInFlying.PacketPlayInPositionLook.class, PacketPlayInFlying.PacketPlayInPositionLook::new).a(PacketPlayInFlying.PacketPlayInLook.class, PacketPlayInFlying.PacketPlayInLook::new).a(PacketPlayInFlying.class, PacketPlayInFlying::new).a(PacketPlayInVehicleMove.class, PacketPlayInVehicleMove::new).a(PacketPlayInBoatMove.class, PacketPlayInBoatMove::new).a(PacketPlayInPickItem.class, PacketPlayInPickItem::new).a(PacketPlayInAutoRecipe.class, PacketPlayInAutoRecipe::new).a(PacketPlayInAbilities.class, PacketPlayInAbilities::new).a(PacketPlayInBlockDig.class, PacketPlayInBlockDig::new).a(PacketPlayInEntityAction.class, PacketPlayInEntityAction::new).a(PacketPlayInSteerVehicle.class, PacketPlayInSteerVehicle::new).a(PacketPlayInRecipeSettings.class, PacketPlayInRecipeSettings::new).a(PacketPlayInRecipeDisplayed.class, PacketPlayInRecipeDisplayed::new).a(PacketPlayInItemName.class, PacketPlayInItemName::new).a(PacketPlayInResourcePackStatus.class, PacketPlayInResourcePackStatus::new).a(PacketPlayInAdvancements.class, PacketPlayInAdvancements::new).a(PacketPlayInTrSel.class, PacketPlayInTrSel::new).a(PacketPlayInBeacon.class, PacketPlayInBeacon::new).a(PacketPlayInHeldItemSlot.class, PacketPlayInHeldItemSlot::new).a(PacketPlayInSetCommandBlock.class, PacketPlayInSetCommandBlock::new).a(PacketPlayInSetCommandMinecart.class, PacketPlayInSetCommandMinecart::new).a(PacketPlayInSetCreativeSlot.class, PacketPlayInSetCreativeSlot::new).a(PacketPlayInSetJigsaw.class, PacketPlayInSetJigsaw::new).a(PacketPlayInStruct.class, PacketPlayInStruct::new).a(PacketPlayInUpdateSign.class, PacketPlayInUpdateSign::new).a(PacketPlayInArmAnimation.class, PacketPlayInArmAnimation::new).a(PacketPlayInSpectate.class, PacketPlayInSpectate::new).a(PacketPlayInUseItem.class, PacketPlayInUseItem::new).a(PacketPlayInBlockPlace.class, PacketPlayInBlockPlace::new))), STATUS(1, b().a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a<>()).a(PacketStatusInStart.class, PacketStatusInStart::new).a(PacketStatusInPing.class, PacketStatusInPing::new)).a(EnumProtocolDirection.CLIENTBOUND, (new EnumProtocol.a<>()).a(PacketStatusOutServerInfo.class, PacketStatusOutServerInfo::new).a(PacketStatusOutPong.class, PacketStatusOutPong::new))), LOGIN(2, b().a(EnumProtocolDirection.CLIENTBOUND, (new EnumProtocol.a<>()).a(PacketLoginOutDisconnect.class, PacketLoginOutDisconnect::new).a(PacketLoginOutEncryptionBegin.class, PacketLoginOutEncryptionBegin::new).a(PacketLoginOutSuccess.class, PacketLoginOutSuccess::new).a(PacketLoginOutSetCompression.class, PacketLoginOutSetCompression::new).a(PacketLoginOutCustomPayload.class, PacketLoginOutCustomPayload::new)).a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a<>()).a(PacketLoginInStart.class, PacketLoginInStart::new).a(PacketLoginInEncryptionBegin.class, PacketLoginInEncryptionBegin::new).a(PacketLoginInCustomPayload.class, PacketLoginInCustomPayload::new))); ++ // Paper - fix decompile error - add generic names to < > like PacketListenerPlayOut ++ HANDSHAKING(-1, b().a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a()).a(PacketHandshakingInSetProtocol.class, PacketHandshakingInSetProtocol::new))), PLAY(0, b().a(EnumProtocolDirection.CLIENTBOUND, (new EnumProtocol.a()).a(PacketPlayOutSpawnEntity.class, PacketPlayOutSpawnEntity::new).a(PacketPlayOutSpawnEntityExperienceOrb.class, PacketPlayOutSpawnEntityExperienceOrb::new).a(PacketPlayOutSpawnEntityLiving.class, PacketPlayOutSpawnEntityLiving::new).a(PacketPlayOutSpawnEntityPainting.class, PacketPlayOutSpawnEntityPainting::new).a(PacketPlayOutNamedEntitySpawn.class, PacketPlayOutNamedEntitySpawn::new).a(PacketPlayOutAnimation.class, PacketPlayOutAnimation::new).a(PacketPlayOutStatistic.class, PacketPlayOutStatistic::new).a(PacketPlayOutBlockBreak.class, PacketPlayOutBlockBreak::new).a(PacketPlayOutBlockBreakAnimation.class, PacketPlayOutBlockBreakAnimation::new).a(PacketPlayOutTileEntityData.class, PacketPlayOutTileEntityData::new).a(PacketPlayOutBlockAction.class, PacketPlayOutBlockAction::new).a(PacketPlayOutBlockChange.class, PacketPlayOutBlockChange::new).a(PacketPlayOutBoss.class, PacketPlayOutBoss::new).a(PacketPlayOutServerDifficulty.class, PacketPlayOutServerDifficulty::new).a(PacketPlayOutChat.class, PacketPlayOutChat::new).a(PacketPlayOutTabComplete.class, PacketPlayOutTabComplete::new).a(PacketPlayOutCommands.class, PacketPlayOutCommands::new).a(PacketPlayOutTransaction.class, PacketPlayOutTransaction::new).a(PacketPlayOutCloseWindow.class, PacketPlayOutCloseWindow::new).a(PacketPlayOutWindowItems.class, PacketPlayOutWindowItems::new).a(PacketPlayOutWindowData.class, PacketPlayOutWindowData::new).a(PacketPlayOutSetSlot.class, PacketPlayOutSetSlot::new).a(PacketPlayOutSetCooldown.class, PacketPlayOutSetCooldown::new).a(PacketPlayOutCustomPayload.class, PacketPlayOutCustomPayload::new).a(PacketPlayOutCustomSoundEffect.class, PacketPlayOutCustomSoundEffect::new).a(PacketPlayOutKickDisconnect.class, PacketPlayOutKickDisconnect::new).a(PacketPlayOutEntityStatus.class, PacketPlayOutEntityStatus::new).a(PacketPlayOutExplosion.class, PacketPlayOutExplosion::new).a(PacketPlayOutUnloadChunk.class, PacketPlayOutUnloadChunk::new).a(PacketPlayOutGameStateChange.class, PacketPlayOutGameStateChange::new).a(PacketPlayOutOpenWindowHorse.class, PacketPlayOutOpenWindowHorse::new).a(PacketPlayOutKeepAlive.class, PacketPlayOutKeepAlive::new).a(PacketPlayOutMapChunk.class, PacketPlayOutMapChunk::new).a(PacketPlayOutWorldEvent.class, PacketPlayOutWorldEvent::new).a(PacketPlayOutWorldParticles.class, PacketPlayOutWorldParticles::new).a(PacketPlayOutLightUpdate.class, PacketPlayOutLightUpdate::new).a(PacketPlayOutLogin.class, PacketPlayOutLogin::new).a(PacketPlayOutMap.class, PacketPlayOutMap::new).a(PacketPlayOutOpenWindowMerchant.class, PacketPlayOutOpenWindowMerchant::new).a(PacketPlayOutEntity.PacketPlayOutRelEntityMove.class, PacketPlayOutEntity.PacketPlayOutRelEntityMove::new).a(PacketPlayOutEntity.PacketPlayOutRelEntityMoveLook.class, PacketPlayOutEntity.PacketPlayOutRelEntityMoveLook::new).a(PacketPlayOutEntity.PacketPlayOutEntityLook.class, PacketPlayOutEntity.PacketPlayOutEntityLook::new).a(PacketPlayOutEntity.class, PacketPlayOutEntity::new).a(PacketPlayOutVehicleMove.class, PacketPlayOutVehicleMove::new).a(PacketPlayOutOpenBook.class, PacketPlayOutOpenBook::new).a(PacketPlayOutOpenWindow.class, PacketPlayOutOpenWindow::new).a(PacketPlayOutOpenSignEditor.class, PacketPlayOutOpenSignEditor::new).a(PacketPlayOutAutoRecipe.class, PacketPlayOutAutoRecipe::new).a(PacketPlayOutAbilities.class, PacketPlayOutAbilities::new).a(PacketPlayOutCombatEvent.class, PacketPlayOutCombatEvent::new).a(PacketPlayOutPlayerInfo.class, PacketPlayOutPlayerInfo::new).a(PacketPlayOutLookAt.class, PacketPlayOutLookAt::new).a(PacketPlayOutPosition.class, PacketPlayOutPosition::new).a(PacketPlayOutRecipes.class, PacketPlayOutRecipes::new).a(PacketPlayOutEntityDestroy.class, PacketPlayOutEntityDestroy::new).a(PacketPlayOutRemoveEntityEffect.class, PacketPlayOutRemoveEntityEffect::new).a(PacketPlayOutResourcePackSend.class, PacketPlayOutResourcePackSend::new).a(PacketPlayOutRespawn.class, PacketPlayOutRespawn::new).a(PacketPlayOutEntityHeadRotation.class, PacketPlayOutEntityHeadRotation::new).a(PacketPlayOutMultiBlockChange.class, PacketPlayOutMultiBlockChange::new).a(PacketPlayOutSelectAdvancementTab.class, PacketPlayOutSelectAdvancementTab::new).a(PacketPlayOutWorldBorder.class, PacketPlayOutWorldBorder::new).a(PacketPlayOutCamera.class, PacketPlayOutCamera::new).a(PacketPlayOutHeldItemSlot.class, PacketPlayOutHeldItemSlot::new).a(PacketPlayOutViewCentre.class, PacketPlayOutViewCentre::new).a(PacketPlayOutViewDistance.class, PacketPlayOutViewDistance::new).a(PacketPlayOutSpawnPosition.class, PacketPlayOutSpawnPosition::new).a(PacketPlayOutScoreboardDisplayObjective.class, PacketPlayOutScoreboardDisplayObjective::new).a(PacketPlayOutEntityMetadata.class, PacketPlayOutEntityMetadata::new).a(PacketPlayOutAttachEntity.class, PacketPlayOutAttachEntity::new).a(PacketPlayOutEntityVelocity.class, PacketPlayOutEntityVelocity::new).a(PacketPlayOutEntityEquipment.class, PacketPlayOutEntityEquipment::new).a(PacketPlayOutExperience.class, PacketPlayOutExperience::new).a(PacketPlayOutUpdateHealth.class, PacketPlayOutUpdateHealth::new).a(PacketPlayOutScoreboardObjective.class, PacketPlayOutScoreboardObjective::new).a(PacketPlayOutMount.class, PacketPlayOutMount::new).a(PacketPlayOutScoreboardTeam.class, PacketPlayOutScoreboardTeam::new).a(PacketPlayOutScoreboardScore.class, PacketPlayOutScoreboardScore::new).a(PacketPlayOutUpdateTime.class, PacketPlayOutUpdateTime::new).a(PacketPlayOutTitle.class, PacketPlayOutTitle::new).a(PacketPlayOutEntitySound.class, PacketPlayOutEntitySound::new).a(PacketPlayOutNamedSoundEffect.class, PacketPlayOutNamedSoundEffect::new).a(PacketPlayOutStopSound.class, PacketPlayOutStopSound::new).a(PacketPlayOutPlayerListHeaderFooter.class, PacketPlayOutPlayerListHeaderFooter::new).a(PacketPlayOutNBTQuery.class, PacketPlayOutNBTQuery::new).a(PacketPlayOutCollect.class, PacketPlayOutCollect::new).a(PacketPlayOutEntityTeleport.class, PacketPlayOutEntityTeleport::new).a(PacketPlayOutAdvancements.class, PacketPlayOutAdvancements::new).a(PacketPlayOutUpdateAttributes.class, PacketPlayOutUpdateAttributes::new).a(PacketPlayOutEntityEffect.class, PacketPlayOutEntityEffect::new).a(PacketPlayOutRecipeUpdate.class, PacketPlayOutRecipeUpdate::new).a(PacketPlayOutTags.class, PacketPlayOutTags::new)).a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a()).a(PacketPlayInTeleportAccept.class, PacketPlayInTeleportAccept::new).a(PacketPlayInTileNBTQuery.class, PacketPlayInTileNBTQuery::new).a(PacketPlayInDifficultyChange.class, PacketPlayInDifficultyChange::new).a(PacketPlayInChat.class, PacketPlayInChat::new).a(PacketPlayInClientCommand.class, PacketPlayInClientCommand::new).a(PacketPlayInSettings.class, PacketPlayInSettings::new).a(PacketPlayInTabComplete.class, PacketPlayInTabComplete::new).a(PacketPlayInTransaction.class, PacketPlayInTransaction::new).a(PacketPlayInEnchantItem.class, PacketPlayInEnchantItem::new).a(PacketPlayInWindowClick.class, PacketPlayInWindowClick::new).a(PacketPlayInCloseWindow.class, PacketPlayInCloseWindow::new).a(PacketPlayInCustomPayload.class, PacketPlayInCustomPayload::new).a(PacketPlayInBEdit.class, PacketPlayInBEdit::new).a(PacketPlayInEntityNBTQuery.class, PacketPlayInEntityNBTQuery::new).a(PacketPlayInUseEntity.class, PacketPlayInUseEntity::new).a(PacketPlayInJigsawGenerate.class, PacketPlayInJigsawGenerate::new).a(PacketPlayInKeepAlive.class, PacketPlayInKeepAlive::new).a(PacketPlayInDifficultyLock.class, PacketPlayInDifficultyLock::new).a(PacketPlayInFlying.PacketPlayInPosition.class, PacketPlayInFlying.PacketPlayInPosition::new).a(PacketPlayInFlying.PacketPlayInPositionLook.class, PacketPlayInFlying.PacketPlayInPositionLook::new).a(PacketPlayInFlying.PacketPlayInLook.class, PacketPlayInFlying.PacketPlayInLook::new).a(PacketPlayInFlying.class, PacketPlayInFlying::new).a(PacketPlayInVehicleMove.class, PacketPlayInVehicleMove::new).a(PacketPlayInBoatMove.class, PacketPlayInBoatMove::new).a(PacketPlayInPickItem.class, PacketPlayInPickItem::new).a(PacketPlayInAutoRecipe.class, PacketPlayInAutoRecipe::new).a(PacketPlayInAbilities.class, PacketPlayInAbilities::new).a(PacketPlayInBlockDig.class, PacketPlayInBlockDig::new).a(PacketPlayInEntityAction.class, PacketPlayInEntityAction::new).a(PacketPlayInSteerVehicle.class, PacketPlayInSteerVehicle::new).a(PacketPlayInRecipeSettings.class, PacketPlayInRecipeSettings::new).a(PacketPlayInRecipeDisplayed.class, PacketPlayInRecipeDisplayed::new).a(PacketPlayInItemName.class, PacketPlayInItemName::new).a(PacketPlayInResourcePackStatus.class, PacketPlayInResourcePackStatus::new).a(PacketPlayInAdvancements.class, PacketPlayInAdvancements::new).a(PacketPlayInTrSel.class, PacketPlayInTrSel::new).a(PacketPlayInBeacon.class, PacketPlayInBeacon::new).a(PacketPlayInHeldItemSlot.class, PacketPlayInHeldItemSlot::new).a(PacketPlayInSetCommandBlock.class, PacketPlayInSetCommandBlock::new).a(PacketPlayInSetCommandMinecart.class, PacketPlayInSetCommandMinecart::new).a(PacketPlayInSetCreativeSlot.class, PacketPlayInSetCreativeSlot::new).a(PacketPlayInSetJigsaw.class, PacketPlayInSetJigsaw::new).a(PacketPlayInStruct.class, PacketPlayInStruct::new).a(PacketPlayInUpdateSign.class, PacketPlayInUpdateSign::new).a(PacketPlayInArmAnimation.class, PacketPlayInArmAnimation::new).a(PacketPlayInSpectate.class, PacketPlayInSpectate::new).a(PacketPlayInUseItem.class, PacketPlayInUseItem::new).a(PacketPlayInBlockPlace.class, PacketPlayInBlockPlace::new))), STATUS(1, b().a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a()).a(PacketStatusInStart.class, PacketStatusInStart::new).a(PacketStatusInPing.class, PacketStatusInPing::new)).a(EnumProtocolDirection.CLIENTBOUND, (new EnumProtocol.a()).a(PacketStatusOutServerInfo.class, PacketStatusOutServerInfo::new).a(PacketStatusOutPong.class, PacketStatusOutPong::new))), LOGIN(2, b().a(EnumProtocolDirection.CLIENTBOUND, (new EnumProtocol.a()).a(PacketLoginOutDisconnect.class, PacketLoginOutDisconnect::new).a(PacketLoginOutEncryptionBegin.class, PacketLoginOutEncryptionBegin::new).a(PacketLoginOutSuccess.class, PacketLoginOutSuccess::new).a(PacketLoginOutSetCompression.class, PacketLoginOutSetCompression::new).a(PacketLoginOutCustomPayload.class, PacketLoginOutCustomPayload::new)).a(EnumProtocolDirection.SERVERBOUND, (new EnumProtocol.a()).a(PacketLoginInStart.class, PacketLoginInStart::new).a(PacketLoginInEncryptionBegin.class, PacketLoginInEncryptionBegin::new).a(PacketLoginInCustomPayload.class, PacketLoginInCustomPayload::new))); + + private static final EnumProtocol[] e = new EnumProtocol[4]; + private static final Map>, EnumProtocol> f = Maps.newHashMap(); +@@ -248,7 +256,7 @@ public enum EnumProtocol { + private final List>> b; + + private a() { +- this.a = (Object2IntMap) SystemUtils.a((Object) (new Object2IntOpenHashMap()), (object2intopenhashmap) -> { ++ this.a = (Object2IntMap) SystemUtils.a(new Object2IntOpenHashMap(), (object2intopenhashmap) -> { // Paper - fix decompile error + object2intopenhashmap.defaultReturnValue(-1); + }); + this.b = Lists.newArrayList(); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index a95c52035177cb86c7e3a580d5c26a5dd20a17e2..81cff54c93bc04d19e3cc0f5307799c62d139ab2 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1675,9 +1675,9 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrantmap(resourcepackrepository::a).filter(Objects::nonNull).map(ResourcePackLoader::d).collect(ImmutableList.toImmutableList()); // CraftBukkit - decompile error // Paper - decompile error + }, this).thenCompose((immutablelist) -> { +- return DataPackResources.a(immutablelist, this.j() ? CommandDispatcher.ServerType.DEDICATED : CommandDispatcher.ServerType.INTEGRATED, this.h(), this.executorService, this); ++ return DataPackResources.a(immutablelist, this.j() ? CommandDispatcher.ServerType.DEDICATED : CommandDispatcher.ServerType.INTEGRATED, this.h(), this.executorService, this); // Paper - decompile error + }).thenAcceptAsync((datapackresources) -> { + this.dataPackResources.close(); + this.dataPackResources = datapackresources; +diff --git a/src/main/java/net/minecraft/server/level/LightEngineThreaded.java b/src/main/java/net/minecraft/server/level/LightEngineThreaded.java +index 5a51f47f747382ec2a30bb47bcb1f7c61dd4c369..e066848127cb9a42e8c39422691cc65132cac6bb 100644 +--- a/src/main/java/net/minecraft/server/level/LightEngineThreaded.java ++++ b/src/main/java/net/minecraft/server/level/LightEngineThreaded.java +@@ -179,7 +179,7 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + + public void queueUpdate() { + if ((!this.c.isEmpty() || super.a()) && this.g.compareAndSet(false, true)) { +- this.b.a((Object) (() -> { ++ this.b.a((() -> { // Paper - decompile error + this.b(); + this.g.set(false); + })); +diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java +index 1c500e1193296f92f03a94e2cf085b215daaad6c..51ef4adf66c1e21093e63ab46fa47e66c2425fdb 100644 +--- a/src/main/java/net/minecraft/server/level/Ticket.java ++++ b/src/main/java/net/minecraft/server/level/Ticket.java +@@ -23,7 +23,7 @@ public final class Ticket implements Comparable> { + } else { + int j = Integer.compare(System.identityHashCode(this.a), System.identityHashCode(ticket.a)); + +- return j != 0 ? j : this.a.a().compare(this.identifier, ticket.identifier); ++ return j != 0 ? j : this.a.a().compare(this.identifier, (T)ticket.identifier); // Paper - decompile fix + } + } + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 1eeb9b9ca01357ae32358737a12fdc2e8cd389d8..634f34b8b88a1ef86cebf3126da9989a88707711 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1920,7 +1920,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + // CraftBukkit - decompile error +- return (String) object2intopenhashmap.object2IntEntrySet().stream().sorted(Comparator.comparing(it.unimi.dsi.fastutil.objects.Object2IntMap.Entry::getIntValue).reversed()).limit(5L).map((it_unimi_dsi_fastutil_objects_object2intmap_entry) -> { ++ return (String) object2intopenhashmap.object2IntEntrySet().stream().sorted(Comparator.comparing(Object2IntMap.Entry::getIntValue).reversed()).limit(5L).map((it_unimi_dsi_fastutil_objects_object2intmap_entry) -> { // Paper - decompile fix + return it_unimi_dsi_fastutil_objects_object2intmap_entry.getKey() + ":" + it_unimi_dsi_fastutil_objects_object2intmap_entry.getIntValue(); + }).collect(Collectors.joining(",")); + } catch (Exception exception) { +diff --git a/src/main/java/net/minecraft/stats/ServerStatisticManager.java b/src/main/java/net/minecraft/stats/ServerStatisticManager.java +index 322a4b584c6223b08581affb2e9919df19c0267b..1efab34e03199879f5e0dcee0ff79ce2c23c73bc 100644 +--- a/src/main/java/net/minecraft/stats/ServerStatisticManager.java ++++ b/src/main/java/net/minecraft/stats/ServerStatisticManager.java +@@ -203,7 +203,7 @@ public class ServerStatisticManager extends StatisticManager { + ObjectIterator objectiterator = this.a.object2IntEntrySet().iterator(); + + while (objectiterator.hasNext()) { +- it.unimi.dsi.fastutil.objects.Object2IntMap.Entry> it_unimi_dsi_fastutil_objects_object2intmap_entry = (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry) objectiterator.next(); ++ Object2IntMap.Entry> it_unimi_dsi_fastutil_objects_object2intmap_entry = (Object2IntMap.Entry) objectiterator.next(); // Paper - decompile fix + Statistic statistic = (Statistic) it_unimi_dsi_fastutil_objects_object2intmap_entry.getKey(); + + ((JsonObject) map.computeIfAbsent(statistic.getWrapper(), (statisticwrapper) -> { +diff --git a/src/main/java/net/minecraft/util/ArraySetSorted.java b/src/main/java/net/minecraft/util/ArraySetSorted.java +index e56b8e172d96c5508457fcf3f5a0cf0d2d2d8d7c..427daa94322f47b4eaf881d85a01fed239db549a 100644 +--- a/src/main/java/net/minecraft/util/ArraySetSorted.java ++++ b/src/main/java/net/minecraft/util/ArraySetSorted.java +@@ -23,11 +23,11 @@ public class ArraySetSorted extends AbstractSet { + } + + public static > ArraySetSorted a(int i) { +- return new ArraySetSorted<>(i, Comparator.naturalOrder()); ++ return new ArraySetSorted<>(i, (Comparator)Comparator.naturalOrder()); // Paper - decompile fix + } + + private static T[] a(Object[] aobject) { +- return (Object[]) aobject; ++ return (T[])aobject; // Paper - decompile fix + } + + private int c(T t0) { +@@ -101,7 +101,7 @@ public class ArraySetSorted extends AbstractSet { + } + + public boolean remove(Object object) { +- int i = this.c(object); ++ int i = this.c((T)object); // Paper - decompile fix + + if (i >= 0) { + this.d(i); +@@ -116,7 +116,7 @@ public class ArraySetSorted extends AbstractSet { + } + + public boolean contains(Object object) { +- int i = this.c(object); ++ int i = this.c((T)object); // Paper - decompile fix + + return i >= 0; + } +@@ -135,7 +135,7 @@ public class ArraySetSorted extends AbstractSet { + + public U[] toArray(U[] au) { + if (au.length < this.c) { +- return (Object[]) Arrays.copyOf(this.b, this.c, au.getClass()); ++ return (U[])Arrays.copyOf(this.b, this.c, au.getClass()); // Paper - decompile fix + } else { + System.arraycopy(this.b, 0, au, 0, this.c); + if (au.length > this.c) { +diff --git a/src/main/java/net/minecraft/util/RegistryID.java b/src/main/java/net/minecraft/util/RegistryID.java +index d88e55c64f80707b4a9b1e271714c2dbdee9a38a..6150f7a5c5004ac79414ab22dbaa3439dc8afdb4 100644 +--- a/src/main/java/net/minecraft/util/RegistryID.java ++++ b/src/main/java/net/minecraft/util/RegistryID.java +@@ -18,11 +18,18 @@ public class RegistryID implements Registry { + + public RegistryID(int i) { + i = (int) ((float) i / 0.8F); +- this.b = (Object[]) (new Object[i]); ++ this.b = (K[]) (new Object[i]); // Paper - decompile fix + this.c = new int[i]; +- this.d = (Object[]) (new Object[i]); ++ this.d = (K[]) (new Object[i]); // Paper - decompile fix + } + ++ // Paper start - decompile fix ++ @Override ++ public int a(K k) { ++ return getId(k); ++ } ++ // Paper end ++ + public int getId(@Nullable K k0) { + return this.c(this.b(k0, this.d(k0))); + } +@@ -56,9 +63,9 @@ public class RegistryID implements Registry { + K[] ak = this.b; + int[] aint = this.c; + +- this.b = (Object[]) (new Object[i]); ++ this.b = (K[]) (new Object[i]); // Paper - decompile fix + this.c = new int[i]; +- this.d = (Object[]) (new Object[i]); ++ this.d = (K[]) (new Object[i]); // Paper - decompile fix + this.e = 0; + this.f = 0; + +diff --git a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +index 6abf1459cc97c261daf3c116521574d31a77a338..2b2c03ab62816f3d21ef953c4a45f55e3997cca6 100644 +--- a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java ++++ b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +@@ -55,7 +55,7 @@ public abstract class IAsyncTaskHandler implements Mailbox implements Mailbox implements Mailbox, AutoCloseable, Runnable { + + public void run() { + try { +- this.a((i) -> { ++ this.a((int i) -> { // Paper - decompile fix + return i == 0; + }); + } finally { +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java +index fe5dcce3873ca2724ac9d416d6f5d3c65d0fdafe..aa1d948e6aebef25f0f4c4c07f5131d2e8387e59 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java +@@ -191,9 +191,9 @@ public class VillagePlace extends RegionFileSection { + } + + private static boolean a(ChunkSection chunksection) { +- Set set = VillagePlaceType.x; ++ Set set = VillagePlaceType.x; // Paper - decompile error + +- set.getClass(); ++ //set.getClass(); // Paper - decompile error + return chunksection.a(set::contains); + } + +@@ -211,7 +211,7 @@ public class VillagePlace extends RegionFileSection { + SectionPosition.b(new ChunkCoordIntPair(blockposition), Math.floorDiv(i, 16)).map((sectionposition) -> { + return Pair.of(sectionposition, this.d(sectionposition.s())); + }).filter((pair) -> { +- return !(Boolean) ((Optional) pair.getSecond()).map(VillagePlaceSection::a).orElse(false); ++ return !(Boolean) (pair.getSecond()).map(VillagePlaceSection::a).orElse(false); // Paper - decompile fix + }).map((pair) -> { + return ((SectionPosition) pair.getFirst()).r(); + }).filter((chunkcoordintpair) -> { +@@ -263,7 +263,7 @@ public class VillagePlace extends RegionFileSection { + + private final Predicate d; + +- private Occupancy(Predicate predicate) { ++ private Occupancy(Predicate predicate) { // Paper - decompile fix + this.d = predicate; + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java b/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java +index fc1f0cd4b70cdd0dda538d8867fab4cb8443120e..c181d5f5e6108ade54fc97c665897d1db5e90719 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java +@@ -65,7 +65,7 @@ public class EntityVindicator extends EntityIllagerAbstract { + this.goalSelector.a(2, new EntityIllagerAbstract.b(this)); + this.goalSelector.a(3, new EntityRaider.a(this, 10.0F)); + this.goalSelector.a(4, new EntityVindicator.c(this)); +- this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a()); ++ this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // Paper - decompile fix + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, true)); + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, true)); +diff --git a/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java +index fe1dde99f758daa730acacc78237d92aa443ab6d..764ff5d9ffb541a356a6bc8b321e619849dde747 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java ++++ b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java +@@ -47,12 +47,12 @@ import net.minecraft.world.level.saveddata.maps.WorldMap; + + public class VillagerTrades { + +- public static final Map> a = (Map) SystemUtils.a((Object) Maps.newHashMap(), (hashmap) -> { ++ public static final Map> a = SystemUtils.a(Maps.newHashMap(), (hashmap) -> { // Paper - decompile fix + hashmap.put(VillagerProfession.FARMER, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.WHEAT, 20, 16, 2), new VillagerTrades.b(Items.POTATO, 26, 16, 2), new VillagerTrades.b(Items.CARROT, 22, 16, 2), new VillagerTrades.b(Items.BEETROOT, 15, 16, 2), new VillagerTrades.h(Items.BREAD, 1, 6, 16, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Blocks.PUMPKIN, 6, 12, 10), new VillagerTrades.h(Items.PUMPKIN_PIE, 1, 4, 5), new VillagerTrades.h(Items.APPLE, 1, 4, 16, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.COOKIE, 3, 18, 10), new VillagerTrades.b(Blocks.MELON, 4, 12, 20)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Blocks.CAKE, 1, 1, 12, 15), new VillagerTrades.i(MobEffects.NIGHT_VISION, 100, 15), new VillagerTrades.i(MobEffects.JUMP, 160, 15), new VillagerTrades.i(MobEffects.WEAKNESS, 140, 15), new VillagerTrades.i(MobEffects.BLINDNESS, 120, 15), new VillagerTrades.i(MobEffects.POISON, 280, 15), new VillagerTrades.i(MobEffects.SATURATION, 7, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.GOLDEN_CARROT, 3, 3, 30), new VillagerTrades.h(Items.GLISTERING_MELON_SLICE, 4, 3, 30)}))); +- hashmap.put(VillagerProfession.FISHERMAN, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.STRING, 20, 16, 2), new VillagerTrades.b(Items.COAL, 10, 16, 2), new VillagerTrades.g(Items.COD, 6, Items.COOKED_COD, 6, 16, 1), new VillagerTrades.h(Items.COD_BUCKET, 3, 1, 16, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.COD, 15, 16, 10), new VillagerTrades.g(Items.SALMON, 6, Items.COOKED_SALMON, 6, 16, 5), new VillagerTrades.h(Items.rn, 2, 1, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.SALMON, 13, 16, 20), new VillagerTrades.e(Items.FISHING_ROD, 3, 3, 10, 0.2F)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.TROPICAL_FISH, 6, 12, 30)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.PUFFERFISH, 4, 12, 30), new VillagerTrades.c(1, 12, 30, ImmutableMap.builder().put(VillagerType.PLAINS, Items.OAK_BOAT).put(VillagerType.TAIGA, Items.SPRUCE_BOAT).put(VillagerType.SNOW, Items.SPRUCE_BOAT).put(VillagerType.DESERT, Items.JUNGLE_BOAT).put(VillagerType.JUNGLE, Items.JUNGLE_BOAT).put(VillagerType.SAVANNA, Items.ACACIA_BOAT).put(VillagerType.SWAMP, Items.DARK_OAK_BOAT).build())}))); ++ hashmap.put(VillagerProfession.FISHERMAN, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.STRING, 20, 16, 2), new VillagerTrades.b(Items.COAL, 10, 16, 2), new VillagerTrades.g(Items.COD, 6, Items.COOKED_COD, 6, 16, 1), new VillagerTrades.h(Items.COD_BUCKET, 3, 1, 16, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.COD, 15, 16, 10), new VillagerTrades.g(Items.SALMON, 6, Items.COOKED_SALMON, 6, 16, 5), new VillagerTrades.h(Items.rn, 2, 1, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.SALMON, 13, 16, 20), new VillagerTrades.e(Items.FISHING_ROD, 3, 3, 10, 0.2F)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.TROPICAL_FISH, 6, 12, 30)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.PUFFERFISH, 4, 12, 30), new VillagerTrades.c(1, 12, 30, ImmutableMap.builder().put(VillagerType.PLAINS, Items.OAK_BOAT).put(VillagerType.TAIGA, Items.SPRUCE_BOAT).put(VillagerType.SNOW, Items.SPRUCE_BOAT).put(VillagerType.DESERT, Items.JUNGLE_BOAT).put(VillagerType.JUNGLE, Items.JUNGLE_BOAT).put(VillagerType.SAVANNA, Items.ACACIA_BOAT).put(VillagerType.SWAMP, Items.DARK_OAK_BOAT).build())}))); // Paper - add to ImmutableMap..builder() + hashmap.put(VillagerProfession.SHEPHERD, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Blocks.WHITE_WOOL, 18, 16, 2), new VillagerTrades.b(Blocks.BROWN_WOOL, 18, 16, 2), new VillagerTrades.b(Blocks.BLACK_WOOL, 18, 16, 2), new VillagerTrades.b(Blocks.GRAY_WOOL, 18, 16, 2), new VillagerTrades.h(Items.SHEARS, 2, 1, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.WHITE_DYE, 12, 16, 10), new VillagerTrades.b(Items.GRAY_DYE, 12, 16, 10), new VillagerTrades.b(Items.BLACK_DYE, 12, 16, 10), new VillagerTrades.b(Items.LIGHT_BLUE_DYE, 12, 16, 10), new VillagerTrades.b(Items.LIME_DYE, 12, 16, 10), new VillagerTrades.h(Blocks.WHITE_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.ORANGE_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.MAGENTA_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.LIGHT_BLUE_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.YELLOW_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.LIME_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.PINK_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.GRAY_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.LIGHT_GRAY_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.CYAN_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.PURPLE_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.BLUE_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.BROWN_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.GREEN_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.RED_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.BLACK_WOOL, 1, 1, 16, 5), new VillagerTrades.h(Blocks.WHITE_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.ORANGE_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.MAGENTA_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.LIGHT_BLUE_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.YELLOW_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.LIME_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.PINK_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.GRAY_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.LIGHT_GRAY_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.CYAN_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.PURPLE_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.BLUE_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.BROWN_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.GREEN_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.RED_CARPET, 1, 4, 16, 5), new VillagerTrades.h(Blocks.BLACK_CARPET, 1, 4, 16, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.YELLOW_DYE, 12, 16, 20), new VillagerTrades.b(Items.LIGHT_GRAY_DYE, 12, 16, 20), new VillagerTrades.b(Items.ORANGE_DYE, 12, 16, 20), new VillagerTrades.b(Items.RED_DYE, 12, 16, 20), new VillagerTrades.b(Items.PINK_DYE, 12, 16, 20), new VillagerTrades.h(Blocks.WHITE_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.YELLOW_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.RED_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.BLACK_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.BLUE_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.BROWN_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.CYAN_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.GRAY_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.GREEN_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.LIGHT_BLUE_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.LIGHT_GRAY_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.LIME_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.MAGENTA_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.ORANGE_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.PINK_BED, 3, 1, 12, 10), new VillagerTrades.h(Blocks.PURPLE_BED, 3, 1, 12, 10)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.BROWN_DYE, 12, 16, 30), new VillagerTrades.b(Items.PURPLE_DYE, 12, 16, 30), new VillagerTrades.b(Items.BLUE_DYE, 12, 16, 30), new VillagerTrades.b(Items.GREEN_DYE, 12, 16, 30), new VillagerTrades.b(Items.MAGENTA_DYE, 12, 16, 30), new VillagerTrades.b(Items.CYAN_DYE, 12, 16, 30), new VillagerTrades.h(Items.WHITE_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.BLUE_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.LIGHT_BLUE_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.RED_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.PINK_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.GREEN_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.LIME_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.GRAY_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.BLACK_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.PURPLE_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.MAGENTA_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.CYAN_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.BROWN_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.YELLOW_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.ORANGE_BANNER, 3, 1, 12, 15), new VillagerTrades.h(Items.LIGHT_GRAY_BANNER, 3, 1, 12, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.PAINTING, 2, 3, 30)}))); + hashmap.put(VillagerProfession.FLETCHER, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.STICK, 32, 16, 2), new VillagerTrades.h(Items.ARROW, 1, 16, 1), new VillagerTrades.g(Blocks.GRAVEL, 10, Items.FLINT, 10, 12, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.FLINT, 26, 12, 10), new VillagerTrades.h(Items.BOW, 2, 1, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.STRING, 14, 16, 20), new VillagerTrades.h(Items.CROSSBOW, 3, 1, 10)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.FEATHER, 24, 16, 30), new VillagerTrades.e(Items.BOW, 2, 3, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.es, 8, 12, 30), new VillagerTrades.e(Items.CROSSBOW, 3, 3, 15), new VillagerTrades.j(Items.ARROW, 5, Items.TIPPED_ARROW, 5, 2, 12, 30)}))); +- hashmap.put(VillagerProfession.LIBRARIAN, a(ImmutableMap.builder().put(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.PAPER, 24, 16, 2), new VillagerTrades.d(1), new VillagerTrades.h(Blocks.BOOKSHELF, 9, 1, 12, 1)}).put(2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.BOOK, 4, 12, 10), new VillagerTrades.d(5), new VillagerTrades.h(Items.rk, 1, 1, 5)}).put(3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.INK_SAC, 5, 12, 20), new VillagerTrades.d(10), new VillagerTrades.h(Items.az, 1, 4, 10)}).put(4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.WRITABLE_BOOK, 2, 12, 30), new VillagerTrades.d(15), new VillagerTrades.h(Items.CLOCK, 5, 1, 15), new VillagerTrades.h(Items.COMPASS, 4, 1, 15)}).put(5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.NAME_TAG, 20, 1, 30)}).build())); ++ hashmap.put(VillagerProfession.LIBRARIAN, a(ImmutableMap.builder().put(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.PAPER, 24, 16, 2), new VillagerTrades.d(1), new VillagerTrades.h(Blocks.BOOKSHELF, 9, 1, 12, 1)}).put(2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.BOOK, 4, 12, 10), new VillagerTrades.d(5), new VillagerTrades.h(Items.rk, 1, 1, 5)}).put(3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.INK_SAC, 5, 12, 20), new VillagerTrades.d(10), new VillagerTrades.h(Items.az, 1, 4, 10)}).put(4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.WRITABLE_BOOK, 2, 12, 30), new VillagerTrades.d(15), new VillagerTrades.h(Items.CLOCK, 5, 1, 15), new VillagerTrades.h(Items.COMPASS, 4, 1, 15)}).put(5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.NAME_TAG, 20, 1, 30)}).build())); // Paper - add to ImmutableMap..builder() + hashmap.put(VillagerProfession.CARTOGRAPHER, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.PAPER, 24, 16, 2), new VillagerTrades.h(Items.MAP, 7, 1, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.dP, 11, 16, 10), new VillagerTrades.k(13, StructureGenerator.MONUMENT, MapIcon.Type.MONUMENT, 12, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.COMPASS, 1, 12, 20), new VillagerTrades.k(14, StructureGenerator.MANSION, MapIcon.Type.MANSION, 12, 10)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.ITEM_FRAME, 7, 1, 15), new VillagerTrades.h(Items.WHITE_BANNER, 3, 1, 15), new VillagerTrades.h(Items.BLUE_BANNER, 3, 1, 15), new VillagerTrades.h(Items.LIGHT_BLUE_BANNER, 3, 1, 15), new VillagerTrades.h(Items.RED_BANNER, 3, 1, 15), new VillagerTrades.h(Items.PINK_BANNER, 3, 1, 15), new VillagerTrades.h(Items.GREEN_BANNER, 3, 1, 15), new VillagerTrades.h(Items.LIME_BANNER, 3, 1, 15), new VillagerTrades.h(Items.GRAY_BANNER, 3, 1, 15), new VillagerTrades.h(Items.BLACK_BANNER, 3, 1, 15), new VillagerTrades.h(Items.PURPLE_BANNER, 3, 1, 15), new VillagerTrades.h(Items.MAGENTA_BANNER, 3, 1, 15), new VillagerTrades.h(Items.CYAN_BANNER, 3, 1, 15), new VillagerTrades.h(Items.BROWN_BANNER, 3, 1, 15), new VillagerTrades.h(Items.YELLOW_BANNER, 3, 1, 15), new VillagerTrades.h(Items.ORANGE_BANNER, 3, 1, 15), new VillagerTrades.h(Items.LIGHT_GRAY_BANNER, 3, 1, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.h(Items.GLOBE_BANNER_PATTERN, 8, 1, 30)}))); + hashmap.put(VillagerProfession.CLERIC, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.ROTTEN_FLESH, 32, 16, 2), new VillagerTrades.h(Items.REDSTONE, 1, 2, 1)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.GOLD_INGOT, 3, 12, 10), new VillagerTrades.h(Items.LAPIS_LAZULI, 1, 1, 5)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.RABBIT_FOOT, 2, 12, 20), new VillagerTrades.h(Blocks.GLOWSTONE, 4, 1, 12, 10)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.SCUTE, 4, 12, 30), new VillagerTrades.b(Items.GLASS_BOTTLE, 9, 12, 30), new VillagerTrades.h(Items.ENDER_PEARL, 5, 1, 15)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.NETHER_WART, 22, 12, 30), new VillagerTrades.h(Items.EXPERIENCE_BOTTLE, 3, 1, 30)}))); + hashmap.put(VillagerProfession.ARMORER, a(ImmutableMap.of(1, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.COAL, 15, 16, 2), new VillagerTrades.h(new ItemStack(Items.IRON_LEGGINGS), 7, 1, 12, 1, 0.2F), new VillagerTrades.h(new ItemStack(Items.IRON_BOOTS), 4, 1, 12, 1, 0.2F), new VillagerTrades.h(new ItemStack(Items.IRON_HELMET), 5, 1, 12, 1, 0.2F), new VillagerTrades.h(new ItemStack(Items.IRON_CHESTPLATE), 9, 1, 12, 1, 0.2F)}, 2, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.IRON_INGOT, 4, 12, 10), new VillagerTrades.h(new ItemStack(Items.rj), 36, 1, 12, 5, 0.2F), new VillagerTrades.h(new ItemStack(Items.CHAINMAIL_BOOTS), 1, 1, 12, 5, 0.2F), new VillagerTrades.h(new ItemStack(Items.CHAINMAIL_LEGGINGS), 3, 1, 12, 5, 0.2F)}, 3, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.b(Items.LAVA_BUCKET, 1, 12, 20), new VillagerTrades.b(Items.DIAMOND, 1, 12, 20), new VillagerTrades.h(new ItemStack(Items.CHAINMAIL_HELMET), 1, 1, 12, 10, 0.2F), new VillagerTrades.h(new ItemStack(Items.CHAINMAIL_CHESTPLATE), 4, 1, 12, 10, 0.2F), new VillagerTrades.h(new ItemStack(Items.SHIELD), 5, 1, 12, 10, 0.2F)}, 4, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.e(Items.DIAMOND_LEGGINGS, 14, 3, 15, 0.2F), new VillagerTrades.e(Items.DIAMOND_BOOTS, 8, 3, 15, 0.2F)}, 5, new VillagerTrades.IMerchantRecipeOption[]{new VillagerTrades.e(Items.DIAMOND_HELMET, 8, 3, 30, 0.2F), new VillagerTrades.e(Items.DIAMOND_CHESTPLATE, 16, 3, 30, 0.2F)}))); +diff --git a/src/main/java/net/minecraft/world/item/crafting/CraftingManager.java b/src/main/java/net/minecraft/world/item/crafting/CraftingManager.java +index 54a636a3d5fdd0107cd0a41167ab1626469fe042..5ba58bf1a47c696235e6e7a4a6815104bc23de80 100644 +--- a/src/main/java/net/minecraft/world/item/crafting/CraftingManager.java ++++ b/src/main/java/net/minecraft/world/item/crafting/CraftingManager.java +@@ -75,7 +75,7 @@ public class CraftingManager extends ResourceDataJson { + } + + this.recipes = (Map) map1.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry1) -> { +- return (entry1.getValue()); // CraftBukkit ++ return entry1.getValue(); // CraftBukkit // Paper - decompile fix - *shrugs internally* + })); + CraftingManager.LOGGER.info("Loaded {} recipes", map1.size()); + } +diff --git a/src/main/java/net/minecraft/world/level/IEntityAccess.java b/src/main/java/net/minecraft/world/level/IEntityAccess.java +index d24f97593777d6929271520f7501a800f1aadaa6..4ece69851e7b05016f52c291ce911eb791cf3a23 100644 +--- a/src/main/java/net/minecraft/world/level/IEntityAccess.java ++++ b/src/main/java/net/minecraft/world/level/IEntityAccess.java +@@ -167,22 +167,22 @@ public interface IEntityAccess { + + @Nullable + default T a(Class oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { +- return this.a(this.a(oclass, axisalignedbb, (Predicate) null), pathfindertargetcondition, entityliving, d0, d1, d2); ++ return this.a(this.a(oclass, axisalignedbb, null), pathfindertargetcondition, entityliving, d0, d1, d2); // Paper - decompile fix + } + + @Nullable + default T b(Class oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { +- return this.a(this.b(oclass, axisalignedbb, (Predicate) null), pathfindertargetcondition, entityliving, d0, d1, d2); ++ return this.a(this.b(oclass, axisalignedbb, null), pathfindertargetcondition, entityliving, d0, d1, d2); // Paper - decompile fix + } + + @Nullable + default T a(List list, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2) { + double d3 = -1.0D; + T t0 = null; +- Iterator iterator = list.iterator(); ++ Iterator iterator = list.iterator(); // Paper - decompile fix + + while (iterator.hasNext()) { +- T t1 = (EntityLiving) iterator.next(); ++ T t1 = iterator.next(); // Paper - decompile fix + + if (pathfindertargetcondition.a(entityliving, t1)) { + double d4 = t1.h(d0, d1, d2); +@@ -215,10 +215,10 @@ public interface IEntityAccess { + default List a(Class oclass, PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving, AxisAlignedBB axisalignedbb) { + List list = this.a(oclass, axisalignedbb, (Predicate) null); + List list1 = Lists.newArrayList(); +- Iterator iterator = list.iterator(); ++ Iterator iterator = list.iterator(); // Paper - decompile fix + + while (iterator.hasNext()) { +- T t0 = (EntityLiving) iterator.next(); ++ T t0 = iterator.next(); // Paper - decompile fix + + if (pathfindertargetcondition.a(entityliving, t0)) { + list1.add(t0); +diff --git a/src/main/java/net/minecraft/world/level/NextTickListEntry.java b/src/main/java/net/minecraft/world/level/NextTickListEntry.java +index 116a5e4ded3ccf935fd143f2512098c22ec2ad76..f3bcb96232d18abbcd86b079a7c5830bb30d75d2 100644 +--- a/src/main/java/net/minecraft/world/level/NextTickListEntry.java ++++ b/src/main/java/net/minecraft/world/level/NextTickListEntry.java +@@ -38,13 +38,13 @@ public class NextTickListEntry { + return this.a.hashCode(); + } + +- public static Comparator> a() { ++ public static Comparator a() { // Paper - decompile fix + return Comparator.comparingLong((nextticklistentry) -> { +- return nextticklistentry.b; ++ return ((NextTickListEntry) nextticklistentry).b; // Paper - decompile fix + }).thenComparing((nextticklistentry) -> { +- return nextticklistentry.c; ++ return ((NextTickListEntry) nextticklistentry).c; // Paper - decompile fix + }).thenComparingLong((nextticklistentry) -> { +- return nextticklistentry.f; ++ return ((NextTickListEntry) nextticklistentry).f; // Paper - decompile fix + }); + } + +diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeBase.java b/src/main/java/net/minecraft/world/level/biome/BiomeBase.java +index 6672d74426d6a334d52f641c48d3a352c2bb6605..7147cdda756ccb3d4f6880802128f68601783883 100644 +--- a/src/main/java/net/minecraft/world/level/biome/BiomeBase.java ++++ b/src/main/java/net/minecraft/world/level/biome/BiomeBase.java +@@ -49,8 +49,15 @@ import org.apache.logging.log4j.Logger; + public final class BiomeBase { + + public static final Logger LOGGER = LogManager.getLogger(); ++ // Paper start ++ private static class dProxy extends BiomeBase.d { ++ private dProxy(Precipitation biomebase_precipitation, float f, TemperatureModifier biomebase_temperaturemodifier, float f1) { ++ super(biomebase_precipitation, f, biomebase_temperaturemodifier, f1); ++ } ++ }; ++ // Paper end + public static final Codec b = RecordCodecBuilder.create((instance) -> { +- return instance.group(BiomeBase.d.a.forGetter((biomebase) -> { ++ return instance.group(dProxy.a.forGetter((biomebase) -> { // Paper + return biomebase.j; + }), BiomeBase.Geography.r.fieldOf("category").forGetter((biomebase) -> { + return biomebase.o; +@@ -67,7 +74,7 @@ public final class BiomeBase { + })).apply(instance, BiomeBase::new); + }); + public static final Codec c = RecordCodecBuilder.create((instance) -> { +- return instance.group(BiomeBase.d.a.forGetter((biomebase) -> { ++ return instance.group(dProxy.a.forGetter((biomebase) -> { // Paper + return biomebase.j; + }), BiomeBase.Geography.r.fieldOf("category").forGetter((biomebase) -> { + return biomebase.o; +diff --git a/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java b/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java +index a549195e67236c0146861b896fb9e4907073af58..8d13e60f40e1b760e9e69969dc3f37bc6c70dbe9 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java +@@ -160,7 +160,7 @@ public class TileEntityPiston extends TileEntity implements ITickable { + private static void a(EnumDirection enumdirection, Entity entity, double d0, EnumDirection enumdirection1) { + TileEntityPiston.h.set(enumdirection); + entity.move(EnumMoveType.PISTON, new Vec3D(d0 * (double) enumdirection1.getAdjacentX(), d0 * (double) enumdirection1.getAdjacentY(), d0 * (double) enumdirection1.getAdjacentZ())); +- TileEntityPiston.h.set((Object) null); ++ TileEntityPiston.h.set(null); // Paper - decompile fix + } + + private void g(float f) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/IBlockDataHolder.java b/src/main/java/net/minecraft/world/level/block/state/IBlockDataHolder.java +index 074bd5f060c6bb80568b72d23ce84c27ba774578..e4b59a85ee9b435b2e86d4c7d78b7224773f6967 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/IBlockDataHolder.java ++++ b/src/main/java/net/minecraft/world/level/block/state/IBlockDataHolder.java +@@ -28,11 +28,11 @@ public abstract class IBlockDataHolder { + } else { + IBlockState iblockstate = (IBlockState) entry.getKey(); + +- return iblockstate.getName() + "=" + this.a(iblockstate, (Comparable) entry.getValue()); ++ return iblockstate.getName() + "=" + this.a((IBlockState) iblockstate, (Comparable) entry.getValue()); // Paper - decompile fix + } + } + +- private > String a(IBlockState iblockstate, Comparable comparable) { ++ private > String a(IBlockState iblockstate, T comparable) { // Paper - decompile error + return iblockstate.a(comparable); + } + }; +@@ -48,11 +48,11 @@ public abstract class IBlockDataHolder { + } + + public > S a(IBlockState iblockstate) { +- return this.set(iblockstate, (Comparable) a(iblockstate.getValues(), (Object) this.get(iblockstate))); ++ return this.set(iblockstate, a(iblockstate.getValues(), this.get(iblockstate))); // Paper - decompile error + } + + protected static T a(Collection collection, T t0) { +- Iterator iterator = collection.iterator(); ++ Iterator iterator = collection.iterator(); // Paper + + do { + if (!iterator.hasNext()) { +@@ -94,7 +94,7 @@ public abstract class IBlockDataHolder { + if (comparable == null) { + throw new IllegalArgumentException("Cannot get property " + iblockstate + " as it does not exist in " + this.c); + } else { +- return (Comparable) iblockstate.getType().cast(comparable); ++ return iblockstate.getType().cast(comparable); // Paper - decompile error + } + } + +@@ -110,7 +110,7 @@ public abstract class IBlockDataHolder { + if (comparable == null) { + throw new IllegalArgumentException("Cannot set property " + iblockstate + " as it does not exist in " + this.c); + } else if (comparable == v0) { +- return this; ++ return (S) this; // Paper - decompile error + } else { + S s0 = this.e.get(iblockstate, v0); + +@@ -162,7 +162,7 @@ public abstract class IBlockDataHolder { + return codec.dispatch("Name", (iblockdataholder) -> { + return iblockdataholder.c; + }, (object) -> { +- S s0 = (IBlockDataHolder) function.apply(object); ++ S s0 = function.apply(object); // Paper - decompile error + + return s0.getStateMap().isEmpty() ? Codec.unit(s0) : s0.d.fieldOf("Properties").codec(); + }); +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateEnum.java b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateEnum.java +index a6aaf0efed5a9c5e458ca04a80a7a5e71a31d886..de85894beae7ee7d276cf2af3daa77377ce131c3 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateEnum.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateEnum.java +@@ -21,10 +21,10 @@ public class BlockStateEnum & INamable> extends IBlockState + protected BlockStateEnum(String s, Class oclass, Collection collection) { + super(s, oclass); + this.a = ImmutableSet.copyOf(collection); +- Iterator iterator = collection.iterator(); ++ Iterator iterator = collection.iterator(); // Paper - decompile fix + + while (iterator.hasNext()) { +- T t0 = (Enum) iterator.next(); ++ T t0 = iterator.next(); // Paper - Decompile fix + String s1 = ((INamable) t0).getName(); + + if (this.b.containsKey(s1)) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IBlockState.java b/src/main/java/net/minecraft/world/level/block/state/properties/IBlockState.java +index 3e6ba74027685c6190426c825736e84cda87ca63..e3969bad5be64bb41e2973751605d6820c16f021 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/IBlockState.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/IBlockState.java +@@ -17,12 +17,10 @@ public abstract class IBlockState> { + private final Codec> e; + + protected IBlockState(String s, Class oclass) { +- this.d = Codec.STRING.comapFlatMap((s1) -> { +- return (DataResult) this.b(s1).map(DataResult::success).orElseGet(() -> { +- return DataResult.error("Unable to read property: " + this + " with value: " + s1); +- }); +- }, this::a); +- this.e = this.d.xmap(this::b, IBlockState.a::b); ++ this.d = Codec.STRING.comapFlatMap((s1) -> this.b(s1).map(DataResult::success).orElseGet(() -> { // Paper - decompile error ++ return DataResult.error("Unable to read property: " + this + " with value: " + s1); ++ }), this::a); ++ this.e = this.d.xmap(this::b, (IBlockState.a param) -> param.b()); // Paper - decompile fix + this.a = oclass; + this.b = s; + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java +index 88c2643a18165bd7a9e6e056b926d6e894ff60d4..859561a5dccba6548967b685b20e8fcfc296db2a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java +@@ -89,7 +89,7 @@ public class IOWorker implements AutoCloseable { + return this.a(() -> { + try { + this.d.a(); +- return Either.left((Object) null); ++ return Either.left(null); // Paper - decompile error + } catch (Exception exception) { + IOWorker.LOGGER.warn("Failed to synchronized chunks", exception); + return Either.right(exception); +@@ -123,13 +123,13 @@ public class IOWorker implements AutoCloseable { + } + + private void c() { +- this.c.a((Object) (new PairedQueue.b(IOWorker.Priority.LOW.ordinal(), this::b))); ++ this.c.a((new PairedQueue.b(IOWorker.Priority.LOW.ordinal(), this::b))); // Paper - decompile error + } + + private void a(ChunkCoordIntPair chunkcoordintpair, IOWorker.a ioworker_a) { + try { + this.d.write(chunkcoordintpair, ioworker_a.a); +- ioworker_a.b.complete((Object) null); ++ ioworker_a.b.complete(null); // Paper - decompile fix + } catch (Exception exception) { + IOWorker.LOGGER.error("Failed to store chunk {}", chunkcoordintpair, exception); + ioworker_a.b.completeExceptionally(exception); +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +index 38c20c4a8521560eac52e06f848ef835103f107f..ab942253a99234565f83ddcba965e25880877f57 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +@@ -430,7 +430,7 @@ public class EnderDragonBattle { + } + } + +- worldgenendtrophy.b((WorldGenFeatureConfiguration) WorldGenFeatureConfiguration.k).a(this.world, this.world.getChunkProvider().getChunkGenerator(), new Random(), this.exitPortalLocation); ++ worldgenendtrophy.b(WorldGenFeatureConfiguration.k).a(this.world, this.world.getChunkProvider().getChunkGenerator(), new Random(), this.exitPortalLocation); // Paper - decompile fix + } + + private EntityEnderDragon o() { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java +index 44ad3fb2551f681b58b82e7c4f56bbc5a3b4486e..6724927be178cb9a358a9276d01894a63154b7b3 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java +@@ -71,13 +71,13 @@ public abstract class StructureGenerator + public static final StructureGenerator BASTION_REMNANT = a("Bastion_Remnant", new WorldGenFeatureBastionRemnant(WorldGenFeatureVillageConfiguration.a), WorldGenStage.Decoration.SURFACE_STRUCTURES); + public static final List> t = ImmutableList.of(StructureGenerator.PILLAGER_OUTPOST, StructureGenerator.VILLAGE, StructureGenerator.NETHER_FOSSIL); + private static final MinecraftKey w = new MinecraftKey("jigsaw"); +- private static final Map x = ImmutableMap.builder().put(new MinecraftKey("nvi"), StructureGenerator.w).put(new MinecraftKey("pcp"), StructureGenerator.w).put(new MinecraftKey("bastionremnant"), StructureGenerator.w).put(new MinecraftKey("runtime"), StructureGenerator.w).build(); ++ private static final Map x = ImmutableMap.builder().put(new MinecraftKey("nvi"), StructureGenerator.w).put(new MinecraftKey("pcp"), StructureGenerator.w).put(new MinecraftKey("bastionremnant"), StructureGenerator.w).put(new MinecraftKey("runtime"), StructureGenerator.w).build(); // Paper - decompile fix + private final Codec>> y; + + private static > F a(String s, F f0, WorldGenStage.Decoration worldgenstage_decoration) { + StructureGenerator.a.put(s.toLowerCase(Locale.ROOT), f0); + StructureGenerator.u.put(f0, worldgenstage_decoration); +- return (StructureGenerator) IRegistry.a(IRegistry.STRUCTURE_FEATURE, s.toLowerCase(Locale.ROOT), (Object) f0); ++ return (F) IRegistry.>a(IRegistry.STRUCTURE_FEATURE, s.toLowerCase(Locale.ROOT), f0); // Paper - decomp fix + } + + public StructureGenerator(Codec codec) { +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java +index 52564cce4146f49a906729b3ed9488a7a829ea3f..befc8f846c772d58ee687ad427bb71206b4dc43e 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java +@@ -34,10 +34,10 @@ public class LightEngineStorageSky extends LightEngineStorage= l) { +diff --git a/src/main/java/net/minecraft/world/level/storage/WorldPersistentData.java b/src/main/java/net/minecraft/world/level/storage/WorldPersistentData.java +index 45c1d79e0bb2fcffea31513c3d003d28140146b9..3910daeaa177639fa8055301304634c2014dc20f 100644 +--- a/src/main/java/net/minecraft/world/level/storage/WorldPersistentData.java ++++ b/src/main/java/net/minecraft/world/level/storage/WorldPersistentData.java +@@ -44,7 +44,7 @@ public class WorldPersistentData { + if (t0 != null) { + return t0; + } else { +- T t1 = (PersistentBase) supplier.get(); ++ T t1 = supplier.get(); // Paper - decompile fix + + this.a(t1); + return t1; +@@ -53,7 +53,7 @@ public class WorldPersistentData { + + @Nullable + public T b(Supplier supplier, String s) { +- PersistentBase persistentbase = (PersistentBase) this.data.get(s); ++ T persistentbase = (T) this.data.get(s); // Paper - decompile fix + + if (persistentbase == null && !this.data.containsKey(s)) { + persistentbase = this.c(supplier, s); +@@ -69,7 +69,7 @@ public class WorldPersistentData { + File file = this.a(s); + + if (file.exists()) { +- T t0 = (PersistentBase) supplier.get(); ++ T t0 = supplier.get(); // Paper - decompile fix + NBTTagCompound nbttagcompound = this.a(s, SharedConstants.getGameVersion().getWorldVersion()); + + t0.a(nbttagcompound.getCompound("data")); +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootEntryAbstract.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootEntryAbstract.java +index 9421e175fffab7014461aa02e7e36d719837ec11..b6abbe9f1de66cd8e9d2e7127813ce56a0446faf 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootEntryAbstract.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootEntryAbstract.java +@@ -43,7 +43,7 @@ public abstract class LootEntryAbstract implements LootEntryChildren { + + // CraftBukkit start + @Override +- public final void a(JsonObject jsonobject, T t0, JsonSerializationContext jsonserializationcontext) { ++ public void a(JsonObject jsonobject, T t0, JsonSerializationContext jsonserializationcontext) { // Paper - remove final + if (!org.apache.commons.lang3.ArrayUtils.isEmpty(t0.d)) { + jsonobject.add("conditions", jsonserializationcontext.serialize(t0.d)); + } +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootSelectorEntry.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootSelectorEntry.java +index 0e3fe138fc11bd7e648296922c651cecaab8e71e..ceb9a1e1b1d55a0a8cd74189450f356b9ad4c46c 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootSelectorEntry.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootSelectorEntry.java +@@ -132,7 +132,7 @@ public abstract class LootSelectorEntry extends LootEntryAbstract { + @Override + public T b(LootItemFunction.a lootitemfunction_a) { + this.c.add(lootitemfunction_a.b()); +- return (LootSelectorEntry.a) this.d(); ++ return this.d(); // Paper - decompile fix + } + + protected LootItemFunction[] a() { +@@ -141,12 +141,12 @@ public abstract class LootSelectorEntry extends LootEntryAbstract { + + public T a(int i) { + this.a = i; +- return (LootSelectorEntry.a) this.d(); ++ return this.d(); // Paper - decompile fix + } + + public T b(int i) { + this.b = i; +- return (LootSelectorEntry.a) this.d(); ++ return this.d(); // Paper - decompile fix + } + } + +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootItemFunctionExplorationMap.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootItemFunctionExplorationMap.java +index f516e7440ed306b1ace9b35ae82f70ca69df51f3..38125a60bad4830db9de3580ab6d85fd122a0689 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootItemFunctionExplorationMap.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootItemFunctionExplorationMap.java +@@ -89,7 +89,7 @@ public class LootItemFunctionExplorationMap extends LootItemFunctionConditional + public b() {} + + public void a(JsonObject jsonobject, LootItemFunctionExplorationMap lootitemfunctionexplorationmap, JsonSerializationContext jsonserializationcontext) { +- super.a(jsonobject, (LootItemFunctionConditional) lootitemfunctionexplorationmap, jsonserializationcontext); ++ super.a(jsonobject, lootitemfunctionexplorationmap, jsonserializationcontext); // Paper - decompile fix + if (!lootitemfunctionexplorationmap.e.equals(LootItemFunctionExplorationMap.a)) { + jsonobject.add("destination", jsonserializationcontext.serialize(lootitemfunctionexplorationmap.e.i())); + } +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMergerList.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMergerList.java +index afd30320da51bf467d66e94f682936ed8db96d90..c58d380b96e81d65d7c254a9e53017e5157769b0 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMergerList.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMergerList.java +@@ -38,7 +38,7 @@ public final class VoxelShapeMergerList implements VoxelShapeMerger { + double d1 = flag4 ? doublelist.getDouble(i++) : doublelist1.getDouble(j++); + + if ((i != 0 && flag2 || flag4 || flag1) && (j != 0 && flag3 || !flag4 || flag)) { +- if (d0 < d1 - 1.0E-7D) { ++ if (!(d0 >= d1 - 1.0E-7D)) { // Paper - decompile error - welcome to hell + this.b.add(i - 1); + this.c.add(j - 1); + this.a.add(d1); diff --git a/patches/server-unmapped/0001/0005-MC-Utils.patch b/patches/server-unmapped/0001/0005-MC-Utils.patch new file mode 100644 index 0000000000..535911d516 --- /dev/null +++ b/patches/server-unmapped/0001/0005-MC-Utils.patch @@ -0,0 +1,4850 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 28 Mar 2016 20:55:47 -0400 +Subject: [PATCH] MC Utils + + +diff --git a/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4029dc68cf35d63aa70c4a76c35bf65a7fc6358f +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java +@@ -0,0 +1,68 @@ ++package com.destroystokyo.paper.util.concurrent; ++ ++import java.util.concurrent.atomic.AtomicLong; ++ ++/** ++ * copied from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/lock/WeakSeqLock.java ++ * @author Spottedleaf ++ */ ++public final class WeakSeqLock { ++ // TODO when the switch to J11 is made, nuke this class from orbit ++ ++ protected final AtomicLong lock = new AtomicLong(); ++ ++ public WeakSeqLock() { ++ //VarHandle.storeStoreFence(); // warn: usages must be checked to ensure this behaviour isn't needed ++ } ++ ++ public void acquireWrite() { ++ // must be release-type write ++ this.lock.lazySet(this.lock.get() + 1); ++ } ++ ++ public boolean canRead(final long read) { ++ return (read & 1) == 0; ++ } ++ ++ public boolean tryAcquireWrite() { ++ this.acquireWrite(); ++ return true; ++ } ++ ++ public void releaseWrite() { ++ // must be acquire-type write ++ final long lock = this.lock.get(); // volatile here acts as store-store ++ this.lock.lazySet(lock + 1); ++ } ++ ++ public void abortWrite() { ++ // must be acquire-type write ++ final long lock = this.lock.get(); // volatile here acts as store-store ++ this.lock.lazySet(lock ^ 1); ++ } ++ ++ public long acquireRead() { ++ int failures = 0; ++ long curr; ++ ++ for (curr = this.lock.get(); !this.canRead(curr); curr = this.lock.get()) { ++ // without j11, our only backoff is the yield() call... ++ ++ if (++failures > 5_000) { /* TODO determine a threshold */ ++ Thread.yield(); ++ } ++ /* Better waiting is beyond the scope of this lock; if it is needed the lock is being misused */ ++ } ++ ++ //VarHandle.loadLoadFence(); // volatile acts as the load-load barrier ++ return curr; ++ } ++ ++ public boolean tryReleaseRead(final long read) { ++ return this.lock.get() == read; // volatile acts as the load-load barrier ++ } ++ ++ public long getSequentialCounter() { ++ return this.lock.get(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java +new file mode 100644 +index 0000000000000000000000000000000000000000..59868f37d14bbc0ece0836095cdad148778995e6 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java +@@ -0,0 +1,162 @@ ++package com.destroystokyo.paper.util.map; ++ ++import com.destroystokyo.paper.util.concurrent.WeakSeqLock; ++import it.unimi.dsi.fastutil.longs.Long2IntMap; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.longs.LongOpenHashSet; ++import it.unimi.dsi.fastutil.objects.ObjectIterator; ++ ++/** ++ * @author Spottedleaf ++ */ ++public class QueuedChangesMapLong2Int { ++ ++ protected final Long2IntOpenHashMap updatingMap; ++ protected final Long2IntOpenHashMap visibleMap; ++ protected final Long2IntOpenHashMap queuedPuts; ++ protected final LongOpenHashSet queuedRemove; ++ ++ protected int queuedDefaultReturnValue; ++ ++ // we use a seqlock as writes are not common. ++ protected final WeakSeqLock updatingMapSeqLock = new WeakSeqLock(); ++ ++ public QueuedChangesMapLong2Int() { ++ this(16, 0.75f); ++ } ++ ++ public QueuedChangesMapLong2Int(final int capacity, final float loadFactor) { ++ this.updatingMap = new Long2IntOpenHashMap(capacity, loadFactor); ++ this.visibleMap = new Long2IntOpenHashMap(capacity, loadFactor); ++ this.queuedPuts = new Long2IntOpenHashMap(); ++ this.queuedRemove = new LongOpenHashSet(); ++ } ++ ++ public void queueDefaultReturnValue(final int dfl) { ++ this.queuedDefaultReturnValue = dfl; ++ this.updatingMap.defaultReturnValue(dfl); ++ } ++ ++ public int queueUpdate(final long k, final int v) { ++ this.queuedRemove.remove(k); ++ this.queuedPuts.put(k, v); ++ ++ return this.updatingMap.put(k, v); ++ } ++ ++ public int queueRemove(final long k) { ++ this.queuedPuts.remove(k); ++ this.queuedRemove.add(k); ++ ++ return this.updatingMap.remove(k); ++ } ++ ++ public int getUpdating(final long k) { ++ return this.updatingMap.get(k); ++ } ++ ++ public int getVisible(final long k) { ++ return this.visibleMap.get(k); ++ } ++ ++ public int getVisibleAsync(final long k) { ++ long readlock; ++ int ret = 0; ++ ++ do { ++ readlock = this.updatingMapSeqLock.acquireRead(); ++ try { ++ ret = this.visibleMap.get(k); ++ } catch (final Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ // ignore... ++ continue; ++ } ++ ++ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); ++ ++ return ret; ++ } ++ ++ public boolean performUpdates() { ++ this.updatingMapSeqLock.acquireWrite(); ++ this.visibleMap.defaultReturnValue(this.queuedDefaultReturnValue); ++ this.updatingMapSeqLock.releaseWrite(); ++ ++ if (this.queuedPuts.isEmpty() && this.queuedRemove.isEmpty()) { ++ return false; ++ } ++ ++ // update puts ++ final ObjectIterator iterator0 = this.queuedPuts.long2IntEntrySet().fastIterator(); ++ while (iterator0.hasNext()) { ++ final Long2IntMap.Entry entry = iterator0.next(); ++ final long key = entry.getLongKey(); ++ final int val = entry.getIntValue(); ++ ++ this.updatingMapSeqLock.acquireWrite(); ++ try { ++ this.visibleMap.put(key, val); ++ } finally { ++ this.updatingMapSeqLock.releaseWrite(); ++ } ++ } ++ ++ this.queuedPuts.clear(); ++ ++ final LongIterator iterator1 = this.queuedRemove.iterator(); ++ while (iterator1.hasNext()) { ++ final long key = iterator1.nextLong(); ++ ++ this.updatingMapSeqLock.acquireWrite(); ++ try { ++ this.visibleMap.remove(key); ++ } finally { ++ this.updatingMapSeqLock.releaseWrite(); ++ } ++ } ++ ++ this.queuedRemove.clear(); ++ ++ return true; ++ } ++ ++ public boolean performUpdatesLockMap() { ++ this.updatingMapSeqLock.acquireWrite(); ++ try { ++ this.visibleMap.defaultReturnValue(this.queuedDefaultReturnValue); ++ ++ if (this.queuedPuts.isEmpty() && this.queuedRemove.isEmpty()) { ++ return false; ++ } ++ ++ // update puts ++ final ObjectIterator iterator0 = this.queuedPuts.long2IntEntrySet().fastIterator(); ++ while (iterator0.hasNext()) { ++ final Long2IntMap.Entry entry = iterator0.next(); ++ final long key = entry.getLongKey(); ++ final int val = entry.getIntValue(); ++ ++ this.visibleMap.put(key, val); ++ } ++ ++ this.queuedPuts.clear(); ++ ++ final LongIterator iterator1 = this.queuedRemove.iterator(); ++ while (iterator1.hasNext()) { ++ final long key = iterator1.nextLong(); ++ ++ this.visibleMap.remove(key); ++ } ++ ++ this.queuedRemove.clear(); ++ ++ return true; ++ } finally { ++ this.updatingMapSeqLock.releaseWrite(); ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7bab31a312463cc963d9621cdc543a281459bd32 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java +@@ -0,0 +1,202 @@ ++package com.destroystokyo.paper.util.map; ++ ++import com.destroystokyo.paper.util.concurrent.WeakSeqLock; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.List; ++ ++/** ++ * @author Spottedleaf ++ */ ++public class QueuedChangesMapLong2Object { ++ ++ protected static final Object REMOVED = new Object(); ++ ++ protected final Long2ObjectLinkedOpenHashMap updatingMap; ++ protected final Long2ObjectLinkedOpenHashMap visibleMap; ++ protected final Long2ObjectLinkedOpenHashMap queuedChanges; ++ ++ // we use a seqlock as writes are not common. ++ protected final WeakSeqLock updatingMapSeqLock = new WeakSeqLock(); ++ ++ public QueuedChangesMapLong2Object() { ++ this(16, 0.75f); // dfl for fastutil ++ } ++ ++ public QueuedChangesMapLong2Object(final int capacity, final float loadFactor) { ++ this.updatingMap = new Long2ObjectLinkedOpenHashMap<>(capacity, loadFactor); ++ this.visibleMap = new Long2ObjectLinkedOpenHashMap<>(capacity, loadFactor); ++ this.queuedChanges = new Long2ObjectLinkedOpenHashMap<>(); ++ } ++ ++ public V queueUpdate(final long k, final V value) { ++ this.queuedChanges.put(k, value); ++ return this.updatingMap.put(k, value); ++ } ++ ++ public V queueRemove(final long k) { ++ this.queuedChanges.put(k, REMOVED); ++ return this.updatingMap.remove(k); ++ } ++ ++ public V getUpdating(final long k) { ++ return this.updatingMap.get(k); ++ } ++ ++ public boolean updatingContainsKey(final long k) { ++ return this.updatingMap.containsKey(k); ++ } ++ ++ public V getVisible(final long k) { ++ return this.visibleMap.get(k); ++ } ++ ++ public boolean visibleContainsKey(final long k) { ++ return this.visibleMap.containsKey(k); ++ } ++ ++ public V getVisibleAsync(final long k) { ++ long readlock; ++ V ret = null; ++ ++ do { ++ readlock = this.updatingMapSeqLock.acquireRead(); ++ ++ try { ++ ret = this.visibleMap.get(k); ++ } catch (final Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ // ignore... ++ continue; ++ } ++ ++ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); ++ ++ return ret; ++ } ++ ++ public boolean visibleContainsKeyAsync(final long k) { ++ long readlock; ++ boolean ret = false; ++ ++ do { ++ readlock = this.updatingMapSeqLock.acquireRead(); ++ ++ try { ++ ret = this.visibleMap.containsKey(k); ++ } catch (final Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ // ignore... ++ continue; ++ } ++ ++ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); ++ ++ return ret; ++ } ++ ++ public Long2ObjectLinkedOpenHashMap getVisibleMap() { ++ return this.visibleMap; ++ } ++ ++ public Long2ObjectLinkedOpenHashMap getUpdatingMap() { ++ return this.updatingMap; ++ } ++ ++ public int getVisibleSize() { ++ return this.visibleMap.size(); ++ } ++ ++ public int getVisibleSizeAsync() { ++ long readlock; ++ int ret; ++ ++ do { ++ readlock = this.updatingMapSeqLock.acquireRead(); ++ ret = this.visibleMap.size(); ++ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); ++ ++ return ret; ++ } ++ ++ // unlike mojang's impl this cannot be used async since it's not a view of an immutable map ++ public Collection getUpdatingValues() { ++ return this.updatingMap.values(); ++ } ++ ++ public List getUpdatingValuesCopy() { ++ return new ArrayList<>(this.updatingMap.values()); ++ } ++ ++ // unlike mojang's impl this cannot be used async since it's not a view of an immutable map ++ public Collection getVisibleValues() { ++ return this.visibleMap.values(); ++ } ++ ++ public List getVisibleValuesCopy() { ++ return new ArrayList<>(this.visibleMap.values()); ++ } ++ ++ public boolean performUpdates() { ++ if (this.queuedChanges.isEmpty()) { ++ return false; ++ } ++ ++ final ObjectBidirectionalIterator> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator(); ++ while (iterator.hasNext()) { ++ final Long2ObjectMap.Entry entry = iterator.next(); ++ final long key = entry.getLongKey(); ++ final Object val = entry.getValue(); ++ ++ this.updatingMapSeqLock.acquireWrite(); ++ try { ++ if (val == REMOVED) { ++ this.visibleMap.remove(key); ++ } else { ++ this.visibleMap.put(key, (V)val); ++ } ++ } finally { ++ this.updatingMapSeqLock.releaseWrite(); ++ } ++ } ++ ++ this.queuedChanges.clear(); ++ return true; ++ } ++ ++ public boolean performUpdatesLockMap() { ++ if (this.queuedChanges.isEmpty()) { ++ return false; ++ } ++ ++ final ObjectBidirectionalIterator> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator(); ++ ++ try { ++ this.updatingMapSeqLock.acquireWrite(); ++ ++ while (iterator.hasNext()) { ++ final Long2ObjectMap.Entry entry = iterator.next(); ++ final long key = entry.getLongKey(); ++ final Object val = entry.getValue(); ++ ++ if (val == REMOVED) { ++ this.visibleMap.remove(key); ++ } else { ++ this.visibleMap.put(key, (V)val); ++ } ++ } ++ } finally { ++ this.updatingMapSeqLock.releaseWrite(); ++ } ++ ++ this.queuedChanges.clear(); ++ return true; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java b/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bf63a0e87f8c9529e473269c0626051c81bb04ea +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java +@@ -0,0 +1,128 @@ ++package com.destroystokyo.paper.util.maplist; ++ ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import net.minecraft.world.level.chunk.Chunk; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.NoSuchElementException; ++ ++// list with O(1) remove & contains ++/** ++ * @author Spottedleaf ++ */ ++public final class ChunkList implements Iterable { ++ ++ protected final Long2IntOpenHashMap chunkToIndex = new Long2IntOpenHashMap(2, 0.8f); ++ { ++ this.chunkToIndex.defaultReturnValue(Integer.MIN_VALUE); ++ } ++ ++ protected static final Chunk[] EMPTY_LIST = new Chunk[0]; ++ ++ protected Chunk[] chunks = EMPTY_LIST; ++ protected int count; ++ ++ public int size() { ++ return this.count; ++ } ++ ++ public boolean contains(final Chunk chunk) { ++ return this.chunkToIndex.containsKey(chunk.coordinateKey); ++ } ++ ++ public boolean remove(final Chunk chunk) { ++ final int index = this.chunkToIndex.remove(chunk.coordinateKey); ++ if (index == Integer.MIN_VALUE) { ++ return false; ++ } ++ ++ // move the entity at the end to this index ++ final int endIndex = --this.count; ++ final Chunk end = this.chunks[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.chunkToIndex.put(end.coordinateKey, index); // update index ++ } ++ this.chunks[index] = end; ++ this.chunks[endIndex] = null; ++ ++ return true; ++ } ++ ++ public boolean add(final Chunk chunk) { ++ final int count = this.count; ++ final int currIndex = this.chunkToIndex.putIfAbsent(chunk.coordinateKey, count); ++ ++ if (currIndex != Integer.MIN_VALUE) { ++ return false; // already in this list ++ } ++ ++ Chunk[] list = this.chunks; ++ ++ if (list.length == count) { ++ // resize required ++ list = this.chunks = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative ++ } ++ ++ list[count] = chunk; ++ this.count = count + 1; ++ ++ return true; ++ } ++ ++ public Chunk getChecked(final int index) { ++ if (index < 0 || index >= this.count) { ++ throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); ++ } ++ return this.chunks[index]; ++ } ++ ++ public Chunk getUnchecked(final int index) { ++ return this.chunks[index]; ++ } ++ ++ public Chunk[] getRawData() { ++ return this.chunks; ++ } ++ ++ public void clear() { ++ this.chunkToIndex.clear(); ++ Arrays.fill(this.chunks, 0, this.count, null); ++ this.count = 0; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return new Iterator() { ++ ++ Chunk lastRet; ++ int current; ++ ++ @Override ++ public boolean hasNext() { ++ return this.current < ChunkList.this.count; ++ } ++ ++ @Override ++ public Chunk next() { ++ if (this.current >= ChunkList.this.count) { ++ throw new NoSuchElementException(); ++ } ++ return this.lastRet = ChunkList.this.chunks[this.current++]; ++ } ++ ++ @Override ++ public void remove() { ++ final Chunk lastRet = this.lastRet; ++ ++ if (lastRet == null) { ++ throw new IllegalStateException(); ++ } ++ this.lastRet = null; ++ ++ ChunkList.this.remove(lastRet); ++ --this.current; ++ } ++ }; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0133ea6feb1ab88f021f66855669f58367e7420b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java +@@ -0,0 +1,128 @@ ++package com.destroystokyo.paper.util.maplist; ++ ++import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; ++import net.minecraft.world.entity.Entity; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.NoSuchElementException; ++ ++// list with O(1) remove & contains ++/** ++ * @author Spottedleaf ++ */ ++public final class EntityList implements Iterable { ++ ++ protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); ++ { ++ this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); ++ } ++ ++ protected static final Entity[] EMPTY_LIST = new Entity[0]; ++ ++ protected Entity[] entities = EMPTY_LIST; ++ protected int count; ++ ++ public int size() { ++ return this.count; ++ } ++ ++ public boolean contains(final Entity entity) { ++ return this.entityToIndex.containsKey(entity.getId()); ++ } ++ ++ public boolean remove(final Entity entity) { ++ final int index = this.entityToIndex.remove(entity.getId()); ++ if (index == Integer.MIN_VALUE) { ++ return false; ++ } ++ ++ // move the entity at the end to this index ++ final int endIndex = --this.count; ++ final Entity end = this.entities[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.entityToIndex.put(end.getId(), index); // update index ++ } ++ this.entities[index] = end; ++ this.entities[endIndex] = null; ++ ++ return true; ++ } ++ ++ public boolean add(final Entity entity) { ++ final int count = this.count; ++ final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count); ++ ++ if (currIndex != Integer.MIN_VALUE) { ++ return false; // already in this list ++ } ++ ++ Entity[] list = this.entities; ++ ++ if (list.length == count) { ++ // resize required ++ list = this.entities = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative ++ } ++ ++ list[count] = entity; ++ this.count = count + 1; ++ ++ return true; ++ } ++ ++ public Entity getChecked(final int index) { ++ if (index < 0 || index >= this.count) { ++ throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); ++ } ++ return this.entities[index]; ++ } ++ ++ public Entity getUnchecked(final int index) { ++ return this.entities[index]; ++ } ++ ++ public Entity[] getRawData() { ++ return this.entities; ++ } ++ ++ public void clear() { ++ this.entityToIndex.clear(); ++ Arrays.fill(this.entities, 0, this.count, null); ++ this.count = 0; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return new Iterator() { ++ ++ Entity lastRet; ++ int current; ++ ++ @Override ++ public boolean hasNext() { ++ return this.current < EntityList.this.count; ++ } ++ ++ @Override ++ public Entity next() { ++ if (this.current >= EntityList.this.count) { ++ throw new NoSuchElementException(); ++ } ++ return this.lastRet = EntityList.this.entities[this.current++]; ++ } ++ ++ @Override ++ public void remove() { ++ final Entity lastRet = this.lastRet; ++ ++ if (lastRet == null) { ++ throw new IllegalStateException(); ++ } ++ this.lastRet = null; ++ ++ EntityList.this.remove(lastRet); ++ --this.current; ++ } ++ }; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d9fdc8196e53518ceac3aeb7bf3b98a0bd348f8f +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java +@@ -0,0 +1,128 @@ ++package com.destroystokyo.paper.util.maplist; ++ ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.ChunkSection; ++import net.minecraft.world.level.chunk.DataPaletteGlobal; ++import java.util.Arrays; ++ ++/** ++ * @author Spottedleaf ++ */ ++public final class IBlockDataList { ++ ++ static final DataPaletteGlobal GLOBAL_PALETTE = (DataPaletteGlobal) ChunkSection.GLOBAL_PALETTE; ++ ++ // map of location -> (index | (location << 16) | (palette id << 32)) ++ private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(2, 0.8f); ++ { ++ this.map.defaultReturnValue(Long.MAX_VALUE); ++ } ++ ++ private static final long[] EMPTY_LIST = new long[0]; ++ ++ private long[] byIndex = EMPTY_LIST; ++ private int size; ++ ++ public static int getLocationKey(final int x, final int y, final int z) { ++ return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4)); ++ } ++ ++ public static IBlockData getBlockDataFromRaw(final long raw) { ++ return GLOBAL_PALETTE.getObject((int)(raw >>> 32)); ++ } ++ ++ public static int getIndexFromRaw(final long raw) { ++ return (int)(raw & 0xFFFF); ++ } ++ ++ public static int getLocationFromRaw(final long raw) { ++ return (int)((raw >>> 16) & 0xFFFF); ++ } ++ ++ public static long getRawFromValues(final int index, final int location, final IBlockData data) { ++ return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.getOrCreateIdFor(data)) << 32); ++ } ++ ++ public static long setIndexRawValues(final long value, final int index) { ++ return value & ~(0xFFFF) | (index); ++ } ++ ++ public long add(final int x, final int y, final int z, final IBlockData data) { ++ return this.add(getLocationKey(x, y, z), data); ++ } ++ ++ public long add(final int location, final IBlockData data) { ++ final long curr = this.map.get((short)location); ++ ++ if (curr == Long.MAX_VALUE) { ++ final int index = this.size++; ++ final long raw = getRawFromValues(index, location, data); ++ this.map.put((short)location, raw); ++ ++ if (index >= this.byIndex.length) { ++ this.byIndex = Arrays.copyOf(this.byIndex, (int)Math.max(4L, this.byIndex.length * 2L)); ++ } ++ ++ this.byIndex[index] = raw; ++ return raw; ++ } else { ++ final int index = getIndexFromRaw(curr); ++ final long raw = this.byIndex[index] = getRawFromValues(index, location, data); ++ ++ this.map.put((short)location, raw); ++ ++ return raw; ++ } ++ } ++ ++ public long remove(final int x, final int y, final int z) { ++ return this.remove(getLocationKey(x, y, z)); ++ } ++ ++ public long remove(final int location) { ++ final long ret = this.map.remove((short)location); ++ final int index = getIndexFromRaw(ret); ++ if (ret == Long.MAX_VALUE) { ++ return ret; ++ } ++ ++ // move the entry at the end to this index ++ final int endIndex = --this.size; ++ final long end = this.byIndex[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index)); ++ } ++ this.byIndex[index] = end; ++ this.byIndex[endIndex] = 0L; ++ ++ return ret; ++ } ++ ++ public int size() { ++ return this.size; ++ } ++ ++ public long getRaw(final int index) { ++ return this.byIndex[index]; ++ } ++ ++ public int getLocation(final int index) { ++ return getLocationFromRaw(this.getRaw(index)); ++ } ++ ++ public IBlockData getData(final int index) { ++ return getBlockDataFromRaw(this.getRaw(index)); ++ } ++ ++ public void clear() { ++ this.size = 0; ++ this.map.clear(); ++ } ++ ++ public LongIterator getRawIterator() { ++ return this.map.values().iterator(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java b/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c3b936f54b3fff418c265639ef223292ccc89356 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java +@@ -0,0 +1,230 @@ ++package com.destroystokyo.paper.util.math; ++ ++/** ++ * @author Spottedleaf ++ */ ++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 long 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 = IntegerUtil.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 ++ ++ int p = 31; ++ ++ // all these variables are UNSIGNED! ++ int t = two31 + (d >>> 31); ++ int anc = t - 1 - t%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 - 32; ++ 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/com/destroystokyo/paper/util/misc/AreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..24407a3e653ba32ef6b921c346571ec734a72245 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java +@@ -0,0 +1,453 @@ ++package com.destroystokyo.paper.util.misc; ++ ++import com.destroystokyo.paper.util.math.IntegerUtil; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import javax.annotation.Nullable; ++import java.util.Iterator; ++ ++/** @author Spottedleaf */ ++public abstract class AreaMap { ++ ++ /* Tested via https://gist.github.com/Spottedleaf/520419c6f41ef348fe9926ce674b7217 */ ++ ++ protected final Object2LongOpenHashMap objectToLastCoordinate = new Object2LongOpenHashMap<>(); ++ protected final Object2IntOpenHashMap objectToViewDistance = new Object2IntOpenHashMap<>(); ++ ++ { ++ this.objectToViewDistance.defaultReturnValue(-1); ++ this.objectToLastCoordinate.defaultReturnValue(Long.MIN_VALUE); ++ } ++ ++ // we use linked for better iteration. ++ // map of: coordinate to set of objects in coordinate ++ protected final Long2ObjectOpenHashMap> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.7f); ++ protected final PooledLinkedHashSets pooledHashSets; ++ ++ protected final ChangeCallback addCallback; ++ protected final ChangeCallback removeCallback; ++ protected final ChangeSourceCallback changeSourceCallback; ++ ++ public AreaMap() { ++ this(new PooledLinkedHashSets<>()); ++ } ++ ++ // let users define a "global" or "shared" pooled sets if they wish ++ public AreaMap(final PooledLinkedHashSets pooledHashSets) { ++ this(pooledHashSets, null, null); ++ } ++ ++ public AreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback) { ++ this(pooledHashSets, addCallback, removeCallback, null); ++ } ++ public AreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback, final ChangeSourceCallback changeSourceCallback) { ++ this.pooledHashSets = pooledHashSets; ++ this.addCallback = addCallback; ++ this.removeCallback = removeCallback; ++ this.changeSourceCallback = changeSourceCallback; ++ } ++ ++ @Nullable ++ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final long key) { ++ return this.areaMap.get(key); ++ } ++ ++ @Nullable ++ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final ChunkCoordIntPair chunkPos) { ++ return this.areaMap.get(MCUtil.getCoordinateKey(chunkPos)); ++ } ++ ++ @Nullable ++ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final int chunkX, final int chunkZ) { ++ return this.areaMap.get(MCUtil.getCoordinateKey(chunkX, chunkZ)); ++ } ++ ++ // Long.MIN_VALUE indicates the object is not mapped ++ public final long getLastCoordinate(final E object) { ++ return this.objectToLastCoordinate.getOrDefault(object, Long.MIN_VALUE); ++ } ++ ++ // -1 indicates the object is not mapped ++ public final int getLastViewDistance(final E object) { ++ return this.objectToViewDistance.getOrDefault(object, -1); ++ } ++ ++ // returns the total number of mapped chunks ++ public final int size() { ++ return this.areaMap.size(); ++ } ++ ++ public final void addOrUpdate(final E object, final int chunkX, final int chunkZ, final int viewDistance) { ++ final int oldViewDistance = this.objectToViewDistance.put(object, viewDistance); ++ final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ final long oldPos = this.objectToLastCoordinate.put(object, newPos); ++ ++ if (oldViewDistance == -1) { ++ this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance); ++ this.addObjectCallback(object, chunkX, chunkZ, viewDistance); ++ } else { ++ this.updateObject(object, oldPos, newPos, oldViewDistance, viewDistance); ++ this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance); ++ } ++ //this.validate(object, viewDistance); ++ } ++ ++ public final boolean update(final E object, final int chunkX, final int chunkZ, final int viewDistance) { ++ final int oldViewDistance = this.objectToViewDistance.replace(object, viewDistance); ++ if (oldViewDistance == -1) { ++ return false; ++ } else { ++ final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ final long oldPos = this.objectToLastCoordinate.put(object, newPos); ++ this.updateObject(object, oldPos, newPos, oldViewDistance, viewDistance); ++ this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance); ++ } ++ //this.validate(object, viewDistance); ++ return true; ++ } ++ ++ // called after the distance map updates ++ protected void updateObjectCallback(final E Object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) { ++ if (newPosition != oldPosition && this.changeSourceCallback != null) { ++ this.changeSourceCallback.accept(Object, oldPosition, newPosition); ++ } ++ } ++ ++ public final boolean add(final E object, final int chunkX, final int chunkZ, final int viewDistance) { ++ final int oldViewDistance = this.objectToViewDistance.putIfAbsent(object, viewDistance); ++ if (oldViewDistance != -1) { ++ return false; ++ } ++ ++ final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ this.objectToLastCoordinate.put(object, newPos); ++ this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance); ++ this.addObjectCallback(object, chunkX, chunkZ, viewDistance); ++ ++ //this.validate(object, viewDistance); ++ ++ return true; ++ } ++ ++ // called after the distance map updates ++ protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {} ++ ++ public final boolean remove(final E object) { ++ final long position = this.objectToLastCoordinate.removeLong(object); ++ final int viewDistance = this.objectToViewDistance.removeInt(object); ++ ++ if (viewDistance == -1) { ++ return false; ++ } ++ ++ final int currentX = MCUtil.getCoordinateX(position); ++ final int currentZ = MCUtil.getCoordinateZ(position); ++ ++ this.removeObject(object, currentX, currentZ, currentX, currentZ, viewDistance); ++ this.removeObjectCallback(object, currentX, currentZ, viewDistance); ++ //this.validate(object, -1); ++ return true; ++ } ++ ++ // called after the distance map updates ++ protected void removeObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {} ++ ++ protected abstract PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(final E object); ++ ++ // expensive op, only for debug ++ protected void validate(final E object, final int viewDistance) { ++ int entiesGot = 0; ++ int expectedEntries = (2 * viewDistance + 1); ++ expectedEntries *= expectedEntries; ++ if (viewDistance < 0) { ++ expectedEntries = 0; ++ } ++ ++ final long currPosition = this.objectToLastCoordinate.getLong(object); ++ ++ final int centerX = MCUtil.getCoordinateX(currPosition); ++ final int centerZ = MCUtil.getCoordinateZ(currPosition); ++ ++ for (Iterator>> iterator = this.areaMap.long2ObjectEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ ++ final Long2ObjectLinkedOpenHashMap.Entry> entry = iterator.next(); ++ final long key = entry.getLongKey(); ++ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet map = entry.getValue(); ++ ++ if (map.referenceCount == 0) { ++ throw new IllegalStateException("Invalid map"); ++ } ++ ++ if (map.contains(object)) { ++ ++entiesGot; ++ ++ final int chunkX = MCUtil.getCoordinateX(key); ++ final int chunkZ = MCUtil.getCoordinateZ(key); ++ ++ final int dist = Math.max(IntegerUtil.branchlessAbs(chunkX - centerX), IntegerUtil.branchlessAbs(chunkZ - centerZ)); ++ ++ if (dist > viewDistance) { ++ throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist); ++ } ++ } ++ } ++ ++ if (entiesGot != expectedEntries) { ++ throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot); ++ } ++ } ++ ++ private void addObjectTo(final E object, final int chunkX, final int chunkZ, final int currChunkX, ++ final int currChunkZ, final int prevChunkX, final int prevChunkZ) { ++ final long key = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ ++ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet empty = this.getEmptySetFor(object); ++ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet current = this.areaMap.putIfAbsent(key, empty); ++ ++ if (current != null) { ++ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet next = this.pooledHashSets.findMapWith(current, object); ++ if (next == current) { ++ throw new IllegalStateException("Expected different map: got " + next.toString()); ++ } ++ this.areaMap.put(key, next); ++ ++ current = next; ++ // fall through to callback ++ } else { ++ current = empty; ++ } ++ ++ if (this.addCallback != null) { ++ try { ++ this.addCallback.accept(object, chunkX, chunkZ, currChunkX, currChunkZ, prevChunkX, prevChunkZ, current); ++ } catch (final Throwable ex) { ++ if (ex instanceof ThreadDeath) { ++ throw (ThreadDeath)ex; ++ } ++ MinecraftServer.LOGGER.error("Add callback for map threw exception ", ex); ++ } ++ } ++ } ++ ++ private void removeObjectFrom(final E object, final int chunkX, final int chunkZ, final int currChunkX, ++ final int currChunkZ, final int prevChunkX, final int prevChunkZ) { ++ final long key = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ ++ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet current = this.areaMap.get(key); ++ ++ if (current == null) { ++ throw new IllegalStateException("Current map may not be null for " + object + ", (" + chunkX + "," + chunkZ + ")"); ++ } ++ ++ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet next = this.pooledHashSets.findMapWithout(current, object); ++ ++ if (next == current) { ++ throw new IllegalStateException("Current map [" + next.toString() + "] should have contained " + object + ", (" + chunkX + "," + chunkZ + ")"); ++ } ++ ++ if (next != null) { ++ this.areaMap.put(key, next); ++ } else { ++ this.areaMap.remove(key); ++ } ++ ++ if (this.removeCallback != null) { ++ try { ++ this.removeCallback.accept(object, chunkX, chunkZ, currChunkX, currChunkZ, prevChunkX, prevChunkZ, next); ++ } catch (final Throwable ex) { ++ if (ex instanceof ThreadDeath) { ++ throw (ThreadDeath)ex; ++ } ++ MinecraftServer.LOGGER.error("Remove callback for map threw exception ", ex); ++ } ++ } ++ } ++ ++ private void addObject(final E object, final int chunkX, final int chunkZ, final int prevChunkX, final int prevChunkZ, final int viewDistance) { ++ final int maxX = chunkX + viewDistance; ++ final int maxZ = chunkZ + viewDistance; ++ final int minX = chunkX - viewDistance; ++ final int minZ = chunkZ - viewDistance; ++ for (int x = minX; x <= maxX; ++x) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ this.addObjectTo(object, x, z, chunkX, chunkZ, prevChunkX, prevChunkZ); ++ } ++ } ++ } ++ ++ private void removeObject(final E object, final int chunkX, final int chunkZ, final int currentChunkX, final int currentChunkZ, final int viewDistance) { ++ final int maxX = chunkX + viewDistance; ++ final int maxZ = chunkZ + viewDistance; ++ final int minX = chunkX - viewDistance; ++ final int minZ = chunkZ - viewDistance; ++ for (int x = minX; x <= maxX; ++x) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ this.removeObjectFrom(object, x, z, currentChunkX, currentChunkZ, chunkX, chunkZ); ++ } ++ } ++ } ++ ++ /* math sign function except 0 returns 1 */ ++ protected static int sign(int val) { ++ return 1 | (val >> (Integer.SIZE - 1)); ++ } ++ ++ private void updateObject(final E object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) { ++ final int toX = MCUtil.getCoordinateX(newPosition); ++ final int toZ = MCUtil.getCoordinateZ(newPosition); ++ final int fromX = MCUtil.getCoordinateX(oldPosition); ++ final int fromZ = MCUtil.getCoordinateZ(oldPosition); ++ ++ final int dx = toX - fromX; ++ final int dz = toZ - fromZ; ++ ++ final int totalX = IntegerUtil.branchlessAbs(fromX - toX); ++ final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ); ++ ++ if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) { ++ // teleported? ++ this.removeObject(object, fromX, fromZ, fromX, fromZ, oldViewDistance); ++ this.addObject(object, toX, toZ, fromX, fromZ, newViewDistance); ++ return; ++ } ++ ++ if (oldViewDistance != newViewDistance) { ++ // remove loop ++ ++ final int oldMinX = fromX - oldViewDistance; ++ final int oldMinZ = fromZ - oldViewDistance; ++ final int oldMaxX = fromX + oldViewDistance; ++ final int oldMaxZ = fromZ + oldViewDistance; ++ for (int currX = oldMinX; currX <= oldMaxX; ++currX) { ++ for (int currZ = oldMinZ; currZ <= oldMaxZ; ++currZ) { ++ ++ // only remove if we're outside the new view distance... ++ if (Math.max(IntegerUtil.branchlessAbs(currX - toX), IntegerUtil.branchlessAbs(currZ - toZ)) > newViewDistance) { ++ this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ ++ // add loop ++ ++ final int newMinX = toX - newViewDistance; ++ final int newMinZ = toZ - newViewDistance; ++ final int newMaxX = toX + newViewDistance; ++ final int newMaxZ = toZ + newViewDistance; ++ for (int currX = newMinX; currX <= newMaxX; ++currX) { ++ for (int currZ = newMinZ; currZ <= newMaxZ; ++currZ) { ++ ++ // only add if we're outside the old view distance... ++ if (Math.max(IntegerUtil.branchlessAbs(currX - fromX), IntegerUtil.branchlessAbs(currZ - fromZ)) > oldViewDistance) { ++ this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ ++ return; ++ } ++ ++ // x axis is width ++ // z axis is height ++ // right refers to the x axis of where we moved ++ // top refers to the z axis of where we moved ++ ++ // same view distance ++ ++ // used for relative positioning ++ final int up = sign(dz); // 1 if dz >= 0, -1 otherwise ++ final int right = sign(dx); // 1 if dx >= 0, -1 otherwise ++ ++ // The area excluded by overlapping the two view distance squares creates four rectangles: ++ // Two on the left, and two on the right. The ones on the left we consider the "removed" section ++ // and on the right the "added" section. ++ // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually ++ // exclusive to the regions they surround. ++ ++ // 4 points of the rectangle ++ int maxX; // exclusive ++ int minX; // inclusive ++ int maxZ; // exclusive ++ int minZ; // inclusive ++ ++ if (dx != 0) { ++ // handle right addition ++ ++ maxX = toX + (oldViewDistance * right) + right; // exclusive ++ minX = fromX + (oldViewDistance * right) + right; // inclusive ++ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive ++ minZ = toZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ ++ if (dz != 0) { ++ // handle up addition ++ ++ maxX = toX + (oldViewDistance * right) + right; // exclusive ++ minX = toX - (oldViewDistance * right); // inclusive ++ maxZ = toZ + (oldViewDistance * up) + up; // exclusive ++ minZ = fromZ + (oldViewDistance * up) + up; // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ ++ if (dx != 0) { ++ // handle left removal ++ ++ maxX = toX - (oldViewDistance * right); // exclusive ++ minX = fromX - (oldViewDistance * right); // inclusive ++ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive ++ minZ = toZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ ++ if (dz != 0) { ++ // handle down removal ++ ++ maxX = fromX + (oldViewDistance * right) + right; // exclusive ++ minX = fromX - (oldViewDistance * right); // inclusive ++ maxZ = toZ - (oldViewDistance * up); // exclusive ++ minZ = fromZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); ++ } ++ } ++ } ++ } ++ ++ @FunctionalInterface ++ public static interface ChangeCallback { ++ ++ // if there is no previous position, then prevPos = Integer.MIN_VALUE ++ void accept(final E object, final int rangeX, final int rangeZ, final int currPosX, final int currPosZ, final int prevPosX, final int prevPosZ, ++ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState); ++ ++ } ++ ++ @FunctionalInterface ++ public static interface ChangeSourceCallback { ++ void accept(final E object, final long prevPos, final long newPos); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..905b76d1d65744fe35f56bb78ef75f49178a6a24 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java +@@ -0,0 +1,175 @@ ++package com.destroystokyo.paper.util.misc; ++ ++import com.destroystokyo.paper.util.math.IntegerUtil; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import net.minecraft.server.MCUtil; ++import net.minecraft.world.level.ChunkCoordIntPair; ++ ++/** @author Spottedleaf */ ++public abstract class DistanceTrackingAreaMap extends AreaMap { ++ ++ // use this map only if you need distance tracking, the tracking here is obviously going to hit harder. ++ ++ protected final Long2IntOpenHashMap chunkToNearestDistance = new Long2IntOpenHashMap(1024, 0.7f); ++ { ++ this.chunkToNearestDistance.defaultReturnValue(-1); ++ } ++ ++ protected final DistanceChangeCallback distanceChangeCallback; ++ ++ public DistanceTrackingAreaMap() { ++ this(new PooledLinkedHashSets<>()); ++ } ++ ++ // let users define a "global" or "shared" pooled sets if they wish ++ public DistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets) { ++ this(pooledHashSets, null, null, null); ++ } ++ ++ public DistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback, ++ final DistanceChangeCallback distanceChangeCallback) { ++ super(pooledHashSets, addCallback, removeCallback); ++ this.distanceChangeCallback = distanceChangeCallback; ++ } ++ ++ // ret -1 if there is nothing mapped ++ public final int getNearestObjectDistance(final long key) { ++ return this.chunkToNearestDistance.get(key); ++ } ++ ++ // ret -1 if there is nothing mapped ++ public final int getNearestObjectDistance(final ChunkCoordIntPair chunkPos) { ++ return this.chunkToNearestDistance.get(MCUtil.getCoordinateKey(chunkPos)); ++ } ++ ++ // ret -1 if there is nothing mapped ++ public final int getNearestObjectDistance(final int chunkX, final int chunkZ) { ++ return this.chunkToNearestDistance.get(MCUtil.getCoordinateKey(chunkX, chunkZ)); ++ } ++ ++ protected final void recalculateDistance(final int chunkX, final int chunkZ) { ++ final long key = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet state = this.areaMap.get(key); ++ if (state == null) { ++ final int oldDistance = this.chunkToNearestDistance.remove(key); ++ // nothing here. ++ if (oldDistance == -1) { ++ // nothing was here previously ++ return; ++ } ++ if (this.distanceChangeCallback != null) { ++ this.distanceChangeCallback.accept(chunkX, chunkZ, oldDistance, -1, null); ++ } ++ return; ++ } ++ ++ int newDistance = Integer.MAX_VALUE; ++ ++ final Object[] rawData = state.getBackingSet(); ++ for (int i = 0, len = rawData.length; i < len; ++i) { ++ final Object raw = rawData[i]; ++ ++ if (raw == null) { ++ continue; ++ } ++ ++ final E object = (E)raw; ++ final long location = this.objectToLastCoordinate.getLong(object); ++ ++ final int distance = Math.max(IntegerUtil.branchlessAbs(chunkX - MCUtil.getCoordinateX(location)), IntegerUtil.branchlessAbs(chunkZ - MCUtil.getCoordinateZ(location))); ++ ++ if (distance < newDistance) { ++ newDistance = distance; ++ } ++ } ++ ++ final int oldDistance = this.chunkToNearestDistance.put(key, newDistance); ++ ++ if (oldDistance != newDistance) { ++ if (this.distanceChangeCallback != null) { ++ this.distanceChangeCallback.accept(chunkX, chunkZ, oldDistance, newDistance, state); ++ } ++ } ++ } ++ ++ @Override ++ protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) { ++ final int maxX = chunkX + viewDistance; ++ final int maxZ = chunkZ + viewDistance; ++ final int minX = chunkX - viewDistance; ++ final int minZ = chunkZ - viewDistance; ++ for (int x = minX; x <= maxX; ++x) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ this.recalculateDistance(x, z); ++ } ++ } ++ } ++ ++ @Override ++ protected void removeObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) { ++ final int maxX = chunkX + viewDistance; ++ final int maxZ = chunkZ + viewDistance; ++ final int minX = chunkX - viewDistance; ++ final int minZ = chunkZ - viewDistance; ++ for (int x = minX; x <= maxX; ++x) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ this.recalculateDistance(x, z); ++ } ++ } ++ } ++ ++ @Override ++ protected void updateObjectCallback(final E object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) { ++ if (oldPosition == newPosition && newViewDistance == oldViewDistance) { ++ return; ++ } ++ ++ final int toX = MCUtil.getCoordinateX(newPosition); ++ final int toZ = MCUtil.getCoordinateZ(newPosition); ++ final int fromX = MCUtil.getCoordinateX(oldPosition); ++ final int fromZ = MCUtil.getCoordinateZ(oldPosition); ++ ++ final int totalX = IntegerUtil.branchlessAbs(fromX - toX); ++ final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ); ++ ++ if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) { ++ // teleported? ++ this.removeObjectCallback(object, fromX, fromZ, oldViewDistance); ++ this.addObjectCallback(object, toX, toZ, newViewDistance); ++ return; ++ } ++ ++ final int minX = Math.min(fromX - oldViewDistance, toX - newViewDistance); ++ final int maxX = Math.max(fromX + oldViewDistance, toX + newViewDistance); ++ final int minZ = Math.min(fromZ - oldViewDistance, toZ - newViewDistance); ++ final int maxZ = Math.max(fromZ + oldViewDistance, toZ + newViewDistance); ++ ++ for (int x = minX; x <= maxX; ++x) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ final int distXOld = IntegerUtil.branchlessAbs(x - fromX); ++ final int distZOld = IntegerUtil.branchlessAbs(z - fromZ); ++ ++ if (Math.max(distXOld, distZOld) <= oldViewDistance) { ++ this.recalculateDistance(x, z); ++ continue; ++ } ++ ++ final int distXNew = IntegerUtil.branchlessAbs(x - toX); ++ final int distZNew = IntegerUtil.branchlessAbs(z - toZ); ++ ++ if (Math.max(distXNew, distZNew) <= newViewDistance) { ++ this.recalculateDistance(x, z); ++ continue; ++ } ++ } ++ } ++ } ++ ++ @FunctionalInterface ++ public static interface DistanceChangeCallback { ++ ++ void accept(final int posX, final int posZ, final int oldNearestDistance, final int newNearestDistance, ++ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet state); ++ ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ed6133b07bc6c4662bd2099ea7dc8aabec37c853 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java +@@ -0,0 +1,32 @@ ++package com.destroystokyo.paper.util.misc; ++ ++import net.minecraft.server.level.EntityPlayer; ++ ++/** ++ * @author Spottedleaf ++ */ ++public final class PlayerAreaMap extends AreaMap { ++ ++ public PlayerAreaMap() { ++ super(); ++ } ++ ++ public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets) { ++ super(pooledHashSets); ++ } ++ ++ public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, ++ final ChangeCallback removeCallback) { ++ this(pooledHashSets, addCallback, removeCallback, null); ++ } ++ ++ public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, ++ final ChangeCallback removeCallback, final ChangeSourceCallback changeSourceCallback) { ++ super(pooledHashSets, addCallback, removeCallback, changeSourceCallback); ++ } ++ ++ @Override ++ protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(final EntityPlayer player) { ++ return player.cachedSingleHashSet; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..eca1fea17184076635563717bb32c81187e2f6f7 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java +@@ -0,0 +1,24 @@ ++package com.destroystokyo.paper.util.misc; ++ ++import net.minecraft.server.level.EntityPlayer; ++ ++public class PlayerDistanceTrackingAreaMap extends DistanceTrackingAreaMap { ++ ++ public PlayerDistanceTrackingAreaMap() { ++ super(); ++ } ++ ++ public PlayerDistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets) { ++ super(pooledHashSets); ++ } ++ ++ public PlayerDistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, ++ final ChangeCallback removeCallback, final DistanceChangeCallback distanceChangeCallback) { ++ super(pooledHashSets, addCallback, removeCallback, distanceChangeCallback); ++ } ++ ++ @Override ++ protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(final EntityPlayer player) { ++ return player.cachedSingleHashSet; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e51104e65a07b6ea7bbbcbb6afb066ef6401cc5b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java +@@ -0,0 +1,287 @@ ++package com.destroystokyo.paper.util.misc; ++ ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; ++import java.lang.ref.WeakReference; ++ ++/** @author Spottedleaf */ ++public class PooledLinkedHashSets { ++ ++ /* Tested via https://gist.github.com/Spottedleaf/a93bb7a8993d6ce142d3efc5932bf573 */ ++ ++ // we really want to avoid that equals() check as much as possible... ++ protected final Object2ObjectOpenHashMap, PooledObjectLinkedOpenHashSet> mapPool = new Object2ObjectOpenHashMap<>(128, 0.25f); ++ ++ protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet current) { ++ if (current.referenceCount == 0) { ++ throw new IllegalStateException("Cannot decrement reference count for " + current); ++ } ++ if (current.referenceCount == -1 || --current.referenceCount > 0) { ++ return; ++ } ++ ++ this.mapPool.remove(current); ++ return; ++ } ++ ++ public PooledObjectLinkedOpenHashSet findMapWith(final PooledObjectLinkedOpenHashSet current, final E object) { ++ final PooledObjectLinkedOpenHashSet cached = current.getAddCache(object); ++ ++ if (cached != null) { ++ decrementReferenceCount(current); ++ ++ if (cached.referenceCount == 0) { ++ // bring the map back from the dead ++ PooledObjectLinkedOpenHashSet contending = this.mapPool.putIfAbsent(cached, cached); ++ if (contending != null) { ++ // a map already exists with the elements we want ++ if (contending.referenceCount != -1) { ++ ++contending.referenceCount; ++ } ++ current.updateAddCache(object, contending); ++ return contending; ++ } ++ ++ cached.referenceCount = 1; ++ } else if (cached.referenceCount != -1) { ++ ++cached.referenceCount; ++ } ++ ++ return cached; ++ } ++ ++ if (!current.add(object)) { ++ return current; ++ } ++ ++ // we use get/put since we use a different key on put ++ PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); ++ ++ if (ret == null) { ++ ret = new PooledObjectLinkedOpenHashSet<>(current); ++ current.remove(object); ++ this.mapPool.put(ret, ret); ++ ret.referenceCount = 1; ++ } else { ++ if (ret.referenceCount != -1) { ++ ++ret.referenceCount; ++ } ++ current.remove(object); ++ } ++ ++ current.updateAddCache(object, ret); ++ ++ decrementReferenceCount(current); ++ return ret; ++ } ++ ++ // rets null if current.size() == 1 ++ public PooledObjectLinkedOpenHashSet findMapWithout(final PooledObjectLinkedOpenHashSet current, final E object) { ++ if (current.set.size() == 1) { ++ decrementReferenceCount(current); ++ return null; ++ } ++ ++ final PooledObjectLinkedOpenHashSet cached = current.getRemoveCache(object); ++ ++ if (cached != null) { ++ decrementReferenceCount(current); ++ ++ if (cached.referenceCount == 0) { ++ // bring the map back from the dead ++ PooledObjectLinkedOpenHashSet contending = this.mapPool.putIfAbsent(cached, cached); ++ if (contending != null) { ++ // a map already exists with the elements we want ++ if (contending.referenceCount != -1) { ++ ++contending.referenceCount; ++ } ++ current.updateRemoveCache(object, contending); ++ return contending; ++ } ++ ++ cached.referenceCount = 1; ++ } else if (cached.referenceCount != -1) { ++ ++cached.referenceCount; ++ } ++ ++ return cached; ++ } ++ ++ if (!current.remove(object)) { ++ return current; ++ } ++ ++ // we use get/put since we use a different key on put ++ PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); ++ ++ if (ret == null) { ++ ret = new PooledObjectLinkedOpenHashSet<>(current); ++ current.add(object); ++ this.mapPool.put(ret, ret); ++ ret.referenceCount = 1; ++ } else { ++ if (ret.referenceCount != -1) { ++ ++ret.referenceCount; ++ } ++ current.add(object); ++ } ++ ++ current.updateRemoveCache(object, ret); ++ ++ decrementReferenceCount(current); ++ return ret; ++ } ++ ++ static final class RawSetObjectLinkedOpenHashSet extends ObjectOpenHashSet { ++ ++ public RawSetObjectLinkedOpenHashSet() { ++ super(); ++ } ++ ++ public RawSetObjectLinkedOpenHashSet(final int capacity) { ++ super(capacity); ++ } ++ ++ public RawSetObjectLinkedOpenHashSet(final int capacity, final float loadFactor) { ++ super(capacity, loadFactor); ++ } ++ ++ @Override ++ public RawSetObjectLinkedOpenHashSet clone() { ++ return (RawSetObjectLinkedOpenHashSet)super.clone(); ++ } ++ ++ public E[] getRawSet() { ++ return this.key; ++ } ++ } ++ ++ public static final class PooledObjectLinkedOpenHashSet { ++ ++ private static final WeakReference NULL_REFERENCE = new WeakReference<>(null); ++ ++ final RawSetObjectLinkedOpenHashSet set; ++ int referenceCount; // -1 if special ++ int hash; // optimize hashcode ++ ++ // add cache ++ WeakReference lastAddObject = NULL_REFERENCE; ++ WeakReference> lastAddMap = NULL_REFERENCE; ++ ++ // remove cache ++ WeakReference lastRemoveObject = NULL_REFERENCE; ++ WeakReference> lastRemoveMap = NULL_REFERENCE; ++ ++ public PooledObjectLinkedOpenHashSet(final PooledLinkedHashSets pooledSets) { ++ this.set = new RawSetObjectLinkedOpenHashSet<>(2, 0.8f); ++ } ++ ++ public PooledObjectLinkedOpenHashSet(final E single) { ++ this((PooledLinkedHashSets)null); ++ this.referenceCount = -1; ++ this.add(single); ++ } ++ ++ public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet other) { ++ this.set = other.set.clone(); ++ this.hash = other.hash; ++ } ++ ++ // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java ++ // generated by https://github.com/skeeto/hash-prospector ++ private static int hash0(int x) { ++ x *= 0x36935555; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ PooledObjectLinkedOpenHashSet getAddCache(final E element) { ++ final E currentAdd = this.lastAddObject.get(); ++ ++ if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) { ++ return null; ++ } ++ ++ return this.lastAddMap.get(); ++ } ++ ++ PooledObjectLinkedOpenHashSet getRemoveCache(final E element) { ++ final E currentRemove = this.lastRemoveObject.get(); ++ ++ if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) { ++ return null; ++ } ++ ++ return this.lastRemoveMap.get(); ++ } ++ ++ void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet map) { ++ this.lastAddObject = new WeakReference<>(element); ++ this.lastAddMap = new WeakReference<>(map); ++ } ++ ++ void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet map) { ++ this.lastRemoveObject = new WeakReference<>(element); ++ this.lastRemoveMap = new WeakReference<>(map); ++ } ++ ++ boolean add(final E element) { ++ boolean added = this.set.add(element); ++ ++ if (added) { ++ this.hash += hash0(element.hashCode()); ++ } ++ ++ return added; ++ } ++ ++ boolean remove(Object element) { ++ boolean removed = this.set.remove(element); ++ ++ if (removed) { ++ this.hash -= hash0(element.hashCode()); ++ } ++ ++ return removed; ++ } ++ ++ public boolean contains(final Object element) { ++ return this.set.contains(element); ++ } ++ ++ public E[] getBackingSet() { ++ return this.set.getRawSet(); ++ } ++ ++ public int size() { ++ return this.set.size(); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.hash; ++ } ++ ++ @Override ++ public boolean equals(final Object other) { ++ if (!(other instanceof PooledObjectLinkedOpenHashSet)) { ++ return false; ++ } ++ if (this.referenceCount == 0) { ++ return other == this; ++ } else { ++ if (other == this) { ++ // Unfortunately we are never equal to our own instance while in use! ++ return false; ++ } ++ return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set); ++ } ++ } ++ ++ @Override ++ public String toString() { ++ return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " + ++ this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString(); ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d0c77068e9a53d1b8bbad0f3f6b420d6bc85f8c8 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java +@@ -0,0 +1,85 @@ ++package com.destroystokyo.paper.util.pooled; ++ ++import net.minecraft.server.MCUtil; ++import org.apache.commons.lang3.mutable.MutableInt; ++ ++import java.util.ArrayDeque; ++import java.util.function.Consumer; ++import java.util.function.Supplier; ++ ++public final class PooledObjects { ++ ++ /** ++ * Wrapper for an object that will be have a cleaner registered for it, and may be automatically returned to pool. ++ */ ++ public class AutoReleased { ++ private final E object; ++ private final Runnable cleaner; ++ ++ public AutoReleased(E object, Runnable cleaner) { ++ this.object = object; ++ this.cleaner = cleaner; ++ } ++ ++ public final E getObject() { ++ return object; ++ } ++ ++ public final Runnable getCleaner() { ++ return cleaner; ++ } ++ } ++ ++ public static final PooledObjects POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 1024); ++ ++ private final Supplier creator; ++ private final Consumer releaser; ++ private final int maxPoolSize; ++ private final ArrayDeque queue; ++ ++ public PooledObjects(final Supplier creator, int maxPoolSize) { ++ this(creator, maxPoolSize, null); ++ } ++ public PooledObjects(final Supplier creator, int maxPoolSize, Consumer releaser) { ++ if (creator == null) { ++ throw new NullPointerException("Creator must not be null"); ++ } ++ if (maxPoolSize <= 0) { ++ throw new IllegalArgumentException("Max pool size must be greater-than 0"); ++ } ++ ++ this.queue = new ArrayDeque<>(maxPoolSize); ++ this.maxPoolSize = maxPoolSize; ++ this.creator = creator; ++ this.releaser = releaser; ++ } ++ ++ public AutoReleased acquireCleaner(Object holder) { ++ return acquireCleaner(holder, this::release); ++ } ++ ++ public AutoReleased acquireCleaner(Object holder, Consumer releaser) { ++ E resource = acquire(); ++ Runnable cleaner = MCUtil.registerCleaner(holder, resource, releaser); ++ return new AutoReleased(resource, cleaner); ++ } ++ ++ public final E acquire() { ++ E value; ++ synchronized (queue) { ++ value = this.queue.pollLast(); ++ } ++ return value != null ? value : this.creator.get(); ++ } ++ ++ public final void release(final E value) { ++ if (this.releaser != null) { ++ this.releaser.accept(value); ++ } ++ synchronized (this.queue) { ++ if (queue.size() < this.maxPoolSize) { ++ this.queue.addLast(value); ++ } ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9df0006c1a283f77c4d01d9fce9062fc1c9bbb1f +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java +@@ -0,0 +1,67 @@ ++package com.destroystokyo.paper.util.set; ++ ++import java.util.Collection; ++ ++/** ++ * @author Spottedleaf ++ */ ++public final class OptimizedSmallEnumSet> { ++ ++ private final Class enumClass; ++ private long backingSet; ++ ++ public OptimizedSmallEnumSet(final Class clazz) { ++ if (clazz == null) { ++ throw new IllegalArgumentException("Null class"); ++ } ++ if (!clazz.isEnum()) { ++ throw new IllegalArgumentException("Class must be enum, not " + clazz.getCanonicalName()); ++ } ++ this.enumClass = clazz; ++ } ++ ++ public boolean addUnchecked(final E element) { ++ final int ordinal = element.ordinal(); ++ final long key = 1L << ordinal; ++ ++ final long prev = this.backingSet; ++ this.backingSet = prev | key; ++ ++ return (prev & key) == 0; ++ } ++ ++ public boolean removeUnchecked(final E element) { ++ final int ordinal = element.ordinal(); ++ final long key = 1L << ordinal; ++ ++ final long prev = this.backingSet; ++ this.backingSet = prev & ~key; ++ ++ return (prev & key) != 0; ++ } ++ ++ public void clear() { ++ this.backingSet = 0L; ++ } ++ ++ public int size() { ++ return Long.bitCount(this.backingSet); ++ } ++ ++ public void addAllUnchecked(final Collection enums) { ++ for (final E element : enums) { ++ if (element == null) { ++ throw new NullPointerException("Null element"); ++ } ++ this.backingSet |= (1L << element.ordinal()); ++ } ++ } ++ ++ public long getBackingSet() { ++ return this.backingSet; ++ } ++ ++ public boolean hasCommonElements(final OptimizedSmallEnumSet other) { ++ return (other.backingSet & this.backingSet) != 0; ++ } ++} +diff --git a/src/main/java/net/minecraft/SystemUtils.java b/src/main/java/net/minecraft/SystemUtils.java +index bc7757b929ecce998094ddcdf51a4703e165a6d6..c8bb06a31242089ad950713bd5f94abbfe12adc8 100644 +--- a/src/main/java/net/minecraft/SystemUtils.java ++++ b/src/main/java/net/minecraft/SystemUtils.java +@@ -78,7 +78,7 @@ public class SystemUtils { + } + + public static long getMonotonicNanos() { +- return SystemUtils.a.getAsLong(); ++ return System.nanoTime(); // Paper + } + + public static long getTimeMillis() { +diff --git a/src/main/java/net/minecraft/core/BaseBlockPosition.java b/src/main/java/net/minecraft/core/BaseBlockPosition.java +index fe3a3ce150de0e689c452b67d480b9d69471b330..25fdd55a7548cfaa45a541ad77f22f33c33e7471 100644 +--- a/src/main/java/net/minecraft/core/BaseBlockPosition.java ++++ b/src/main/java/net/minecraft/core/BaseBlockPosition.java +@@ -18,9 +18,9 @@ public class BaseBlockPosition implements Comparable { + return IntStream.of(new int[]{baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()}); + }); + public static final BaseBlockPosition ZERO = new BaseBlockPosition(0, 0, 0); +- private int a; +- private int b; +- private int e; ++ private int a;public final void setX(final int x) { this.a = x; } // Paper - OBFHELPER ++ private int b;public final void setY(final int y) { this.b = y; } // Paper - OBFHELPER ++ private int e;public final void setZ(final int z) { this.e = z; } // Paper - OBFHELPER + + public BaseBlockPosition(int i, int j, int k) { + this.a = i; +@@ -64,15 +64,15 @@ public class BaseBlockPosition implements Comparable { + return this.e; + } + +- protected void o(int i) { ++ public void o(int i) { // Paper - protected -> public + this.a = i; + } + +- protected void p(int i) { ++ public void p(int i) { // Paper - protected -> public + this.b = i; + } + +- protected void q(int i) { ++ public void q(int i) { // Paper - protected -> public + this.e = i; + } + +@@ -108,6 +108,7 @@ public class BaseBlockPosition implements Comparable { + return this.distanceSquared(iposition.getX(), iposition.getY(), iposition.getZ(), true) < d0 * d0; + } + ++ public final double distanceSquared(BaseBlockPosition baseblockposition) { return j(baseblockposition); } // Paper - OBFHELPER + public double j(BaseBlockPosition baseblockposition) { + return this.distanceSquared((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ(), true); + } +diff --git a/src/main/java/net/minecraft/core/BlockPosition.java b/src/main/java/net/minecraft/core/BlockPosition.java +index 76675ad1633dbaebb180842b9914fac18741c62e..9fb6db18c5c1f39b5a564c0f5f70498825defa97 100644 +--- a/src/main/java/net/minecraft/core/BlockPosition.java ++++ b/src/main/java/net/minecraft/core/BlockPosition.java +@@ -105,6 +105,7 @@ public class BlockPosition extends BaseBlockPosition { + return d0 == 0.0D && d1 == 0.0D && d2 == 0.0D ? this : new BlockPosition((double) this.getX() + d0, (double) this.getY() + d1, (double) this.getZ() + d2); + } + ++ public final BlockPosition add(int i, int j, int k) {return b(i, j, k);} // Paper - OBFHELPER + public BlockPosition b(int i, int j, int k) { + return i == 0 && j == 0 && k == 0 ? this : new BlockPosition(this.getX() + i, this.getY() + j, this.getZ() + k); + } +@@ -436,6 +437,7 @@ public class BlockPosition extends BaseBlockPosition { + return super.a(enumblockrotation).immutableCopy(); + } + ++ public final BlockPosition.MutableBlockPosition setValues(int i, int j, int k) { return d(i, j, k);} // Paper - OBFHELPER + public BlockPosition.MutableBlockPosition d(int i, int j, int k) { + this.o(i); + this.p(j); +@@ -443,6 +445,7 @@ public class BlockPosition extends BaseBlockPosition { + return this; + } + ++ public final BlockPosition.MutableBlockPosition setValues(double d0, double d1, double d2) { return c(d0, d1, d2);} // Paper - OBFHELPER + public BlockPosition.MutableBlockPosition c(double d0, double d1, double d2) { + return this.d(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2)); + } +@@ -496,6 +499,7 @@ public class BlockPosition extends BaseBlockPosition { + } + } + ++ /* // Paper start - comment out useless overrides @Override + @Override + public void o(int i) { + super.o(i); +@@ -506,10 +510,10 @@ public class BlockPosition extends BaseBlockPosition { + super.p(i); + } + +- @Override + public void q(int i) { + super.q(i); + } ++ */ // Paper end + + @Override + public BlockPosition immutableCopy() { +diff --git a/src/main/java/net/minecraft/core/RegistryBlockID.java b/src/main/java/net/minecraft/core/RegistryBlockID.java +index e543b6927280a14e1d1220534758289934e31282..d5bec8b0e155ea5ae5746b6da571754a98e4125e 100644 +--- a/src/main/java/net/minecraft/core/RegistryBlockID.java ++++ b/src/main/java/net/minecraft/core/RegistryBlockID.java +@@ -64,6 +64,7 @@ public class RegistryBlockID implements Registry { + return Iterators.filter(this.c.iterator(), Predicates.notNull()); + } + ++ public int size() { return this.a(); } // Paper - OBFHELPER + public int a() { + return this.b.size(); + } +diff --git a/src/main/java/net/minecraft/nbt/NBTTagCompound.java b/src/main/java/net/minecraft/nbt/NBTTagCompound.java +index b2fb24e9ae19ab6e7039a98fc0c265f801be8a99..bf4826e90976fed2ae95e84cadc7f29433af1ddf 100644 +--- a/src/main/java/net/minecraft/nbt/NBTTagCompound.java ++++ b/src/main/java/net/minecraft/nbt/NBTTagCompound.java +@@ -76,7 +76,7 @@ public class NBTTagCompound implements NBTBase { + return "TAG_Compound"; + } + }; +- private final Map map; ++ public final Map map; // Paper + + protected NBTTagCompound(Map map) { + this.map = map; +@@ -139,10 +139,16 @@ public class NBTTagCompound implements NBTBase { + this.map.put(s, NBTTagLong.a(i)); + } + ++ public void setUUID(String prefix, UUID uuid) { a(prefix, uuid); } // Paper - OBFHELPER + public void a(String s, UUID uuid) { + this.map.put(s, GameProfileSerializer.a(uuid)); + } + ++ ++ /** ++ * You must use {@link #hasUUID(String)} before or else it will throw an NPE. ++ */ ++ public UUID getUUID(String prefix) { return a(prefix); } // Paper - OBFHELPER + public UUID a(String s) { + return GameProfileSerializer.a(this.get(s)); + } +diff --git a/src/main/java/net/minecraft/network/NetworkManager.java b/src/main/java/net/minecraft/network/NetworkManager.java +index 82d8a163df294e68b4db685b95553637f905db48..f093b465b868e6003bb2b5ee634a624b5b054493 100644 +--- a/src/main/java/net/minecraft/network/NetworkManager.java ++++ b/src/main/java/net/minecraft/network/NetworkManager.java +@@ -168,6 +168,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + + } + ++ private void dispatchPacket(Packet packet, @Nullable GenericFutureListener> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER + private void b(Packet packet, @Nullable GenericFutureListener> genericfuturelistener) { + EnumProtocol enumprotocol = EnumProtocol.a(packet); + EnumProtocol enumprotocol1 = (EnumProtocol) this.channel.attr(NetworkManager.c).get(); +@@ -208,6 +209,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + + } + ++ private void sendPacketQueue() { this.p(); } // Paper - OBFHELPER + private void p() { + if (this.channel != null && this.channel.isOpen()) { + Queue queue = this.packetQueue; +@@ -344,9 +346,9 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + + static class QueuedPacket { + +- private final Packet a; ++ private final Packet a; private final Packet getPacket() { return this.a; } // Paper - OBFHELPER + @Nullable +- private final GenericFutureListener> b; ++ private final GenericFutureListener> b; private final GenericFutureListener> getGenericFutureListener() { return this.b; } // Paper - OBFHELPER + + public QueuedPacket(Packet packet, @Nullable GenericFutureListener> genericfuturelistener) { + this.a = packet; +diff --git a/src/main/java/net/minecraft/network/PacketDataSerializer.java b/src/main/java/net/minecraft/network/PacketDataSerializer.java +index f94e96406d6dc9c75091ae7563dd5b325f4f0c22..5413bf93f7f0f4491fca1f07c47a925fdace7751 100644 +--- a/src/main/java/net/minecraft/network/PacketDataSerializer.java ++++ b/src/main/java/net/minecraft/network/PacketDataSerializer.java +@@ -49,6 +49,7 @@ public class PacketDataSerializer extends ByteBuf { + this.a = bytebuf; + } + ++ public static int countBytes(int i) { return PacketDataSerializer.a(i); } // Paper - OBFHELPER + public static int a(int i) { + for (int j = 1; j < 5; ++j) { + if ((i & -1 << j * 7) == 0) { +diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java +index 06098698e4adc31aa96f9592975e441f965b5558..dc8cc8d6c00176c8562086282f726dc1b24b2c65 100644 +--- a/src/main/java/net/minecraft/network/PacketEncoder.java ++++ b/src/main/java/net/minecraft/network/PacketEncoder.java +@@ -44,6 +44,7 @@ public class PacketEncoder extends MessageToByteEncoder> { + packet.b(packetdataserializer); + } catch (Throwable throwable) { + PacketEncoder.LOGGER.error(throwable); ++ throwable.printStackTrace(); // Paper - WHAT WAS IT? WHO DID THIS TO YOU? WHAT DID YOU SEE? + if (packet.a()) { + throw new SkipEncodeException(throwable); + } else { +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInBEdit.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInBEdit.java +index d748e07f8870023e74796910a457d58ee0361ca6..2b8358995e4933d3fc3498407a7df7475d7b7e26 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInBEdit.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInBEdit.java +@@ -7,7 +7,7 @@ import net.minecraft.world.item.ItemStack; + + public class PacketPlayInBEdit implements Packet { + +- private ItemStack a; ++ private ItemStack a; public ItemStack getBook() { return a; } // Paper - OBFHELPER + private boolean b; + private int c; + +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +index 820ba7c59e7bc7b6f3311f1a4ec3d724e265a2af..b6b55d5baa5e8a6b69a3e4865c06bc8a4d61a4f3 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +@@ -28,7 +28,7 @@ public class PacketPlayOutMapChunk implements Packet { + private NBTTagCompound d; + @Nullable + private int[] e; +- private byte[] f; ++ private byte[] f; private byte[] getData() { return this.f; } // Paper - OBFHELPER + private List g; + private boolean h; + +@@ -140,6 +140,7 @@ public class PacketPlayOutMapChunk implements Packet { + return bytebuf; + } + ++ public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, int chunkSectionSelector) { return this.a(packetDataSerializer, chunk, chunkSectionSelector); } // Paper - OBFHELPER + public int a(PacketDataSerializer packetdataserializer, Chunk chunk, int i) { + int j = 0; + ChunkSection[] achunksection = chunk.getSections(); +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..18b56b59fd6efd618e6ff6f9cf3a02f57588d244 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -0,0 +1,510 @@ ++package net.minecraft.server; ++ ++import com.destroystokyo.paper.block.TargetBlockInfo; ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.RayTrace; ++import net.minecraft.world.level.World; ++import org.apache.commons.lang.exception.ExceptionUtils; ++import org.bukkit.Location; ++import org.bukkit.block.BlockFace; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.util.Waitable; ++import org.spigotmc.AsyncCatcher; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import java.util.List; ++import java.util.Queue; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.ExecutionException; ++import java.util.concurrent.LinkedBlockingQueue; ++import java.util.concurrent.ThreadPoolExecutor; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.TimeoutException; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.function.BiConsumer; ++import java.util.function.Consumer; ++import java.util.function.Supplier; ++ ++public final class MCUtil { ++ public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor( ++ 0, 2, 60L, TimeUnit.SECONDS, ++ new LinkedBlockingQueue(), ++ new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build() ++ ); ++ public static final ThreadPoolExecutor cleanerExecutor = new ThreadPoolExecutor( ++ 1, 1, 0L, TimeUnit.SECONDS, ++ new LinkedBlockingQueue(), ++ new ThreadFactoryBuilder().setNameFormat("Paper Object Cleaner").build() ++ ); ++ ++ public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE); ++ ++ ++ public static Runnable once(Runnable run) { ++ AtomicBoolean ran = new AtomicBoolean(false); ++ return () -> { ++ if (ran.compareAndSet(false, true)) { ++ run.run(); ++ } ++ }; ++ } ++ ++ public static Runnable once(List list, Consumer cb) { ++ return once(() -> { ++ list.forEach(cb); ++ }); ++ } ++ ++ private static Runnable makeCleanerCallback(Runnable run) { ++ return once(() -> cleanerExecutor.execute(run)); ++ } ++ ++ /** ++ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! ++ * @param obj ++ * @param run ++ * @return ++ */ ++ public static Runnable registerCleaner(Object obj, Runnable run) { ++ // Wrap callback in its own method above or the lambda will leak object ++ Runnable cleaner = makeCleanerCallback(run); ++ co.aikar.cleaner.Cleaner.register(obj, cleaner); ++ return cleaner; ++ } ++ ++ /** ++ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! ++ * @param obj ++ * @param list ++ * @param cleaner ++ * @param ++ * @return ++ */ ++ public static Runnable registerListCleaner(Object obj, List list, Consumer cleaner) { ++ return registerCleaner(obj, () -> { ++ list.forEach(cleaner); ++ list.clear(); ++ }); ++ } ++ ++ /** ++ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! ++ * @param obj ++ * @param resource ++ * @param cleaner ++ * @param ++ * @return ++ */ ++ public static Runnable registerCleaner(Object obj, T resource, java.util.function.Consumer cleaner) { ++ return registerCleaner(obj, () -> cleaner.accept(resource)); ++ } ++ ++ public static List getSpiralOutChunks(BlockPosition blockposition, int radius) { ++ List list = com.google.common.collect.Lists.newArrayList(); ++ ++ list.add(new ChunkCoordIntPair(blockposition.getX() >> 4, blockposition.getZ() >> 4)); ++ for (int r = 1; r <= radius; 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) { ++ list.add(new ChunkCoordIntPair((blockposition.getX() + (x << 4)) >> 4, (blockposition.getZ() + (z << 4)) >> 4)); ++ list.add(new ChunkCoordIntPair((blockposition.getX() - (x << 4)) >> 4, (blockposition.getZ() - (z << 4)) >> 4)); ++ ++ if (x < r) { ++ x++; ++ } else { ++ z--; ++ } ++ } ++ } ++ return list; ++ } ++ ++ public static int fastFloor(double x) { ++ int truncated = (int)x; ++ return x < (double)truncated ? truncated - 1 : truncated; ++ } ++ ++ public static int fastFloor(float x) { ++ int truncated = (int)x; ++ return x < (double)truncated ? truncated - 1 : truncated; ++ } ++ ++ public static float normalizeYaw(float f) { ++ float f1 = f % 360.0F; ++ ++ if (f1 >= 180.0F) { ++ f1 -= 360.0F; ++ } ++ ++ if (f1 < -180.0F) { ++ f1 += 360.0F; ++ } ++ ++ return f1; ++ } ++ ++ /** ++ * Quickly generate a stack trace for current location ++ * ++ * @return Stacktrace ++ */ ++ public static String stack() { ++ return ExceptionUtils.getFullStackTrace(new Throwable()); ++ } ++ ++ /** ++ * Quickly generate a stack trace for current location with message ++ * ++ * @param str ++ * @return Stacktrace ++ */ ++ public static String stack(String str) { ++ return ExceptionUtils.getFullStackTrace(new Throwable(str)); ++ } ++ ++ public static long getCoordinateKey(final BlockPosition blockPos) { ++ return ((long)(blockPos.getZ() >> 4) << 32) | ((blockPos.getX() >> 4) & 0xFFFFFFFFL); ++ } ++ ++ public static long getCoordinateKey(final Entity entity) { ++ return ((long)(MCUtil.fastFloor(entity.locZ()) >> 4) << 32) | ((MCUtil.fastFloor(entity.locX()) >> 4) & 0xFFFFFFFFL); ++ } ++ ++ public static long getCoordinateKey(final ChunkCoordIntPair pair) { ++ return ((long)pair.z << 32) | (pair.x & 0xFFFFFFFFL); ++ } ++ ++ public static long getCoordinateKey(final int x, final int z) { ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ } ++ ++ public static int getCoordinateX(final long key) { ++ return (int)key; ++ } ++ ++ public static int getCoordinateZ(final long key) { ++ return (int)(key >>> 32); ++ } ++ ++ public static int getChunkCoordinate(final double coordinate) { ++ return MCUtil.fastFloor(coordinate) >> 4; ++ } ++ ++ public static int getBlockCoordinate(final double coordinate) { ++ return MCUtil.fastFloor(coordinate); ++ } ++ ++ 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 BlockPosition pos) { ++ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54); ++ } ++ ++ public static long getBlockKey(final Entity entity) { ++ return getBlockKey(getBlockCoordinate(entity.locX()), getBlockCoordinate(entity.locY()), getBlockCoordinate(entity.locZ())); ++ } ++ ++ // assumes the sets have the same comparator, and if this comparator is null then assume T is Comparable ++ public static void mergeSortedSets(final java.util.function.Consumer consumer, final java.util.Comparator comparator, final java.util.SortedSet...sets) { ++ final ObjectRBTreeSet all = new ObjectRBTreeSet<>(comparator); ++ // note: this is done in log(n!) ~ nlogn time. It could be improved if it were to mimic what mergesort does. ++ for (java.util.SortedSet set : sets) { ++ if (set != null) { ++ all.addAll(set); ++ } ++ } ++ all.forEach(consumer); ++ } ++ ++ private MCUtil() {} ++ ++ public static final java.util.concurrent.Executor MAIN_EXECUTOR = (run) -> { ++ if (!isMainThread()) { ++ MinecraftServer.getServer().execute(run); ++ } else { ++ run.run(); ++ } ++ }; ++ ++ public static CompletableFuture ensureMain(CompletableFuture future) { ++ return future.thenApplyAsync(r -> r, MAIN_EXECUTOR); ++ } ++ ++ public static void thenOnMain(CompletableFuture future, Consumer consumer) { ++ future.thenAcceptAsync(consumer, MAIN_EXECUTOR); ++ } ++ public static void thenOnMain(CompletableFuture future, BiConsumer consumer) { ++ future.whenCompleteAsync(consumer, MAIN_EXECUTOR); ++ } ++ ++ public static boolean isMainThread() { ++ return MinecraftServer.getServer().isMainThread(); ++ } ++ ++ public static org.bukkit.scheduler.BukkitTask scheduleTask(int ticks, Runnable runnable) { ++ return scheduleTask(ticks, runnable, null); ++ } ++ ++ public static org.bukkit.scheduler.BukkitTask scheduleTask(int ticks, Runnable runnable, String taskName) { ++ return MinecraftServer.getServer().server.getScheduler().scheduleInternalTask(runnable, ticks, taskName); ++ } ++ ++ public static void processQueue() { ++ Runnable runnable; ++ Queue processQueue = getProcessQueue(); ++ while ((runnable = processQueue.poll()) != null) { ++ try { ++ runnable.run(); ++ } catch (Exception e) { ++ MinecraftServer.LOGGER.error("Error executing task", e); ++ } ++ } ++ } ++ public static T processQueueWhileWaiting(CompletableFuture future) { ++ try { ++ if (isMainThread()) { ++ while (!future.isDone()) { ++ try { ++ return future.get(1, TimeUnit.MILLISECONDS); ++ } catch (TimeoutException ignored) { ++ processQueue(); ++ } ++ } ++ } ++ return future.get(); ++ } catch (Exception e) { ++ throw new RuntimeException(e); ++ } ++ } ++ ++ public static void ensureMain(Runnable run) { ++ ensureMain(null, run); ++ } ++ /** ++ * Ensures the target code is running on the main thread ++ * @param reason ++ * @param run ++ * @return ++ */ ++ public static void ensureMain(String reason, Runnable run) { ++ if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) { ++ if (reason != null) { ++ new IllegalStateException("Asynchronous " + reason + "!").printStackTrace(); ++ } ++ getProcessQueue().add(run); ++ return; ++ } ++ run.run(); ++ } ++ ++ private static Queue getProcessQueue() { ++ return MinecraftServer.getServer().processQueue; ++ } ++ ++ public static T ensureMain(Supplier run) { ++ return ensureMain(null, run); ++ } ++ /** ++ * Ensures the target code is running on the main thread ++ * @param reason ++ * @param run ++ * @param ++ * @return ++ */ ++ public static T ensureMain(String reason, Supplier run) { ++ if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) { ++ if (reason != null) { ++ new IllegalStateException("Asynchronous " + reason + "! Blocking thread until it returns ").printStackTrace(); ++ } ++ Waitable wait = new Waitable() { ++ @Override ++ protected T evaluate() { ++ return run.get(); ++ } ++ }; ++ getProcessQueue().add(wait); ++ try { ++ return wait.get(); ++ } catch (InterruptedException | ExecutionException e) { ++ e.printStackTrace(); ++ } ++ return null; ++ } ++ return run.get(); ++ } ++ ++ /** ++ * Calculates distance between 2 entities ++ * @param e1 ++ * @param e2 ++ * @return ++ */ ++ public static double distance(Entity e1, Entity e2) { ++ return Math.sqrt(distanceSq(e1, e2)); ++ } ++ ++ ++ /** ++ * Calculates distance between 2 block positions ++ * @param e1 ++ * @param e2 ++ * @return ++ */ ++ public static double distance(BlockPosition e1, BlockPosition e2) { ++ return Math.sqrt(distanceSq(e1, e2)); ++ } ++ ++ /** ++ * Gets the distance between 2 positions ++ * @param x1 ++ * @param y1 ++ * @param z1 ++ * @param x2 ++ * @param y2 ++ * @param z2 ++ * @return ++ */ ++ public static double distance(double x1, double y1, double z1, double x2, double y2, double z2) { ++ return Math.sqrt(distanceSq(x1, y1, z1, x2, y2, z2)); ++ } ++ ++ /** ++ * Get's the distance squared between 2 entities ++ * @param e1 ++ * @param e2 ++ * @return ++ */ ++ public static double distanceSq(Entity e1, Entity e2) { ++ return distanceSq(e1.locX(),e1.locY(),e1.locZ(), e2.locX(),e2.locY(),e2.locZ()); ++ } ++ ++ /** ++ * Gets the distance sqaured between 2 block positions ++ * @param pos1 ++ * @param pos2 ++ * @return ++ */ ++ public static double distanceSq(BlockPosition pos1, BlockPosition pos2) { ++ return distanceSq(pos1.getX(), pos1.getY(), pos1.getZ(), pos2.getX(), pos2.getY(), pos2.getZ()); ++ } ++ ++ /** ++ * Gets the distance squared between 2 positions ++ * @param x1 ++ * @param y1 ++ * @param z1 ++ * @param x2 ++ * @param y2 ++ * @param z2 ++ * @return ++ */ ++ public static double distanceSq(double x1, double y1, double z1, double x2, double y2, double z2) { ++ return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2); ++ } ++ ++ /** ++ * Converts a NMS World/BlockPosition to Bukkit Location ++ * @param world ++ * @param x ++ * @param y ++ * @param z ++ * @return ++ */ ++ public static Location toLocation(World world, double x, double y, double z) { ++ return new Location(world.getWorld(), x, y, z); ++ } ++ ++ /** ++ * Converts a NMS World/BlockPosition to Bukkit Location ++ * @param world ++ * @param pos ++ * @return ++ */ ++ public static Location toLocation(World world, BlockPosition pos) { ++ return new Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ /** ++ * Converts an NMS entity's current location to a Bukkit Location ++ * @param entity ++ * @return ++ */ ++ public static Location toLocation(Entity entity) { ++ return new Location(entity.getWorld().getWorld(), entity.locX(), entity.locY(), entity.locZ()); ++ } ++ ++ public static org.bukkit.block.Block toBukkitBlock(World world, BlockPosition pos) { ++ return world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ public static BlockPosition toBlockPosition(Location loc) { ++ return new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); ++ } ++ ++ public static boolean isEdgeOfChunk(BlockPosition pos) { ++ final int modX = pos.getX() & 15; ++ final int modZ = pos.getZ() & 15; ++ return (modX == 0 || modX == 15 || modZ == 0 || modZ == 15); ++ } ++ ++ /** ++ * Posts a task to be executed asynchronously ++ * @param run ++ */ ++ public static void scheduleAsyncTask(Runnable run) { ++ asyncExecutor.execute(run); ++ } ++ ++ @Nonnull ++ public static WorldServer getNMSWorld(@Nonnull org.bukkit.World world) { ++ return ((CraftWorld) world).getHandle(); ++ } ++ ++ public static WorldServer getNMSWorld(@Nonnull org.bukkit.entity.Entity entity) { ++ return getNMSWorld(entity.getWorld()); ++ } ++ ++ public static RayTrace.FluidCollisionOption getNMSFluidCollisionOption(TargetBlockInfo.FluidMode fluidMode) { ++ if (fluidMode == TargetBlockInfo.FluidMode.NEVER) { ++ return RayTrace.FluidCollisionOption.NONE; ++ } ++ if (fluidMode == TargetBlockInfo.FluidMode.SOURCE_ONLY) { ++ return RayTrace.FluidCollisionOption.SOURCE_ONLY; ++ } ++ if (fluidMode == TargetBlockInfo.FluidMode.ALWAYS) { ++ return RayTrace.FluidCollisionOption.ANY; ++ } ++ return null; ++ } ++ ++ public static BlockFace toBukkitBlockFace(EnumDirection enumDirection) { ++ switch (enumDirection) { ++ case DOWN: ++ return BlockFace.DOWN; ++ case UP: ++ return BlockFace.UP; ++ case NORTH: ++ return BlockFace.NORTH; ++ case SOUTH: ++ return BlockFace.SOUTH; ++ case WEST: ++ return BlockFace.WEST; ++ case EAST: ++ return BlockFace.EAST; ++ default: ++ return null; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 81cff54c93bc04d19e3cc0f5307799c62d139ab2..cd3e7b8a23e40c717829bd262bfa675e4e3532f9 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -884,6 +884,9 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant public + private final LightEngineThreaded lightEngine; + private final ChunkProviderServer.a serverThreadQueue; + public final PlayerChunkMap playerChunkMap; +@@ -62,6 +63,158 @@ public class ChunkProviderServer extends IChunkProvider { + private final IChunkAccess[] cacheChunk = new IChunkAccess[4]; + @Nullable + private SpawnerCreature.d p; ++ // Paper start ++ final com.destroystokyo.paper.util.concurrent.WeakSeqLock loadedChunkMapSeqLock = new com.destroystokyo.paper.util.concurrent.WeakSeqLock(); ++ final Long2ObjectOpenHashMap loadedChunkMap = new Long2ObjectOpenHashMap<>(8192, 0.5f); ++ ++ private final Chunk[] lastLoadedChunks = new Chunk[4 * 4]; ++ ++ private static int getChunkCacheKey(int x, int z) { ++ return x & 3 | ((z & 3) << 2); ++ } ++ ++ public void addLoadedChunk(Chunk chunk) { ++ this.loadedChunkMapSeqLock.acquireWrite(); ++ try { ++ this.loadedChunkMap.put(chunk.coordinateKey, chunk); ++ } finally { ++ this.loadedChunkMapSeqLock.releaseWrite(); ++ } ++ ++ // rewrite cache if we have to ++ // we do this since we also cache null chunks ++ int cacheKey = getChunkCacheKey(chunk.locX, chunk.locZ); ++ ++ this.lastLoadedChunks[cacheKey] = chunk; ++ } ++ ++ public void removeLoadedChunk(Chunk chunk) { ++ this.loadedChunkMapSeqLock.acquireWrite(); ++ try { ++ this.loadedChunkMap.remove(chunk.coordinateKey); ++ } finally { ++ this.loadedChunkMapSeqLock.releaseWrite(); ++ } ++ ++ // rewrite cache if we have to ++ // we do this since we also cache null chunks ++ int cacheKey = getChunkCacheKey(chunk.locX, chunk.locZ); ++ ++ Chunk cachedChunk = this.lastLoadedChunks[cacheKey]; ++ if (cachedChunk != null && cachedChunk.coordinateKey == chunk.coordinateKey) { ++ this.lastLoadedChunks[cacheKey] = null; ++ } ++ } ++ ++ public final Chunk getChunkAtIfLoadedMainThread(int x, int z) { ++ int cacheKey = getChunkCacheKey(x, z); ++ ++ Chunk cachedChunk = this.lastLoadedChunks[cacheKey]; ++ if (cachedChunk != null && cachedChunk.locX == x & cachedChunk.locZ == z) { ++ return this.lastLoadedChunks[cacheKey]; ++ } ++ ++ long chunkKey = ChunkCoordIntPair.pair(x, z); ++ ++ cachedChunk = this.loadedChunkMap.get(chunkKey); ++ // Skipping a null check to avoid extra instructions to improve inline capability ++ this.lastLoadedChunks[cacheKey] = cachedChunk; ++ return cachedChunk; ++ } ++ ++ public final Chunk getChunkAtIfLoadedMainThreadNoCache(int x, int z) { ++ return this.loadedChunkMap.get(ChunkCoordIntPair.pair(x, z)); ++ } ++ ++ public final Chunk getChunkAtMainThread(int x, int z) { ++ Chunk ret = this.getChunkAtIfLoadedMainThread(x, z); ++ if (ret != null) { ++ return ret; ++ } ++ return (Chunk)this.getChunkAt(x, z, ChunkStatus.FULL, true); ++ } ++ ++ private long chunkFutureAwaitCounter; ++ ++ public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { ++ if (Thread.currentThread() != this.serverThread) { ++ this.serverThreadQueue.execute(() -> { ++ ChunkProviderServer.this.getEntityTickingChunkAsync(x, z, onLoad); ++ }); ++ return; ++ } ++ this.getChunkFutureAsynchronously(x, z, 31, PlayerChunk::getEntityTickingFuture, onLoad); ++ } ++ ++ public void getTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { ++ if (Thread.currentThread() != this.serverThread) { ++ this.serverThreadQueue.execute(() -> { ++ ChunkProviderServer.this.getTickingChunkAsync(x, z, onLoad); ++ }); ++ return; ++ } ++ this.getChunkFutureAsynchronously(x, z, 32, PlayerChunk::getTickingFuture, onLoad); ++ } ++ ++ public void getFullChunkAsync(int x, int z, java.util.function.Consumer onLoad) { ++ if (Thread.currentThread() != this.serverThread) { ++ this.serverThreadQueue.execute(() -> { ++ ChunkProviderServer.this.getFullChunkAsync(x, z, onLoad); ++ }); ++ return; ++ } ++ this.getChunkFutureAsynchronously(x, z, 33, PlayerChunk::getFullChunkFuture, onLoad); ++ } ++ ++ private void getChunkFutureAsynchronously(int x, int z, int ticketLevel, Function>> futureGet, java.util.function.Consumer onLoad) { ++ if (Thread.currentThread() != this.serverThread) { ++ throw new IllegalStateException(); ++ } ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(x, z); ++ Long identifier = this.chunkFutureAwaitCounter++; ++ this.chunkMapDistance.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); ++ this.tickDistanceManager(); ++ ++ PlayerChunk chunk = this.playerChunkMap.getUpdatingChunk(chunkPos.pair()); ++ ++ if (chunk == null) { ++ throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.world.getWorld().getName() + "'"); ++ } ++ ++ CompletableFuture> future = futureGet.apply(chunk); ++ ++ future.whenCompleteAsync((either, throwable) -> { ++ try { ++ if (throwable != null) { ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ net.minecraft.server.MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", throwable); ++ } else if (either.right().isPresent()) { ++ net.minecraft.server.MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "': " + either.right().get().toString()); ++ } ++ ++ try { ++ if (onLoad != null) { ++ playerChunkMap.callbackExecutor.execute(() -> { ++ onLoad.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback. ++ }); ++ } ++ } catch (Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ net.minecraft.server.MinecraftServer.LOGGER.fatal("Load callback for future await failed " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", thr); ++ return; ++ } ++ } finally { ++ // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback. ++ ChunkProviderServer.this.chunkMapDistance.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); ++ ChunkProviderServer.this.chunkMapDistance.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); ++ } ++ }, this.serverThreadQueue); ++ } ++ // Paper end + + public ChunkProviderServer(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator chunkgenerator, int i, boolean flag, WorldLoadListener worldloadlistener, Supplier supplier) { + this.world = worldserver; +@@ -123,6 +276,49 @@ public class ChunkProviderServer extends IChunkProvider { + this.cacheChunk[0] = ichunkaccess; + } + ++ // Paper start - "real" get chunk if loaded ++ // Note: Partially copied from the getChunkAt method below ++ @Nullable ++ public Chunk getChunkAtIfCachedImmediately(int x, int z) { ++ long k = ChunkCoordIntPair.pair(x, z); ++ ++ // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe ++ ++ PlayerChunk playerChunk = this.getChunk(k); ++ if (playerChunk == null) { ++ return null; ++ } ++ ++ return playerChunk.getFullChunkIfCached(); ++ } ++ ++ @Nullable ++ public Chunk getChunkAtIfLoadedImmediately(int x, int z) { ++ long k = ChunkCoordIntPair.pair(x, z); ++ ++ if (Thread.currentThread() == this.serverThread) { ++ return this.getChunkAtIfLoadedMainThread(x, z); ++ } ++ ++ Chunk ret = null; ++ long readlock; ++ do { ++ readlock = this.loadedChunkMapSeqLock.acquireRead(); ++ try { ++ ret = this.loadedChunkMap.get(k); ++ } catch (Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ // re-try, this means a CME occurred... ++ continue; ++ } ++ } while (!this.loadedChunkMapSeqLock.tryReleaseRead(readlock)); ++ ++ return ret; ++ } ++ // Paper end ++ + @Nullable + @Override + public IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag) { +@@ -406,10 +602,9 @@ public class ChunkProviderServer extends IChunkProvider { + + this.p = spawnercreature_d; + this.world.getMethodProfiler().exit(); +- List list = Lists.newArrayList(this.playerChunkMap.f()); +- +- Collections.shuffle(list); +- list.forEach((playerchunk) -> { ++ //List list = Lists.newArrayList(this.playerChunkMap.f()); // Paper ++ //Collections.shuffle(list); // Paper ++ this.playerChunkMap.f().forEach((playerchunk) -> { // Paper - no... just no... + Optional optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + + if (optional.isPresent()) { +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index f1459d4d2b2592aea1fc39257255979f0a8c61e5..e818bf022b74cae34a512d8c98b47ec3e5c74b9a 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -223,6 +223,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public Integer clientViewDistance; + // CraftBukkit end + ++ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper ++ + public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) { + super(worldserver, worldserver.getSpawn(), worldserver.v(), gameprofile); + this.spawnDimension = World.OVERWORLD; +@@ -235,6 +237,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.c(worldserver); + this.co = minecraftserver.a(this); + ++ this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper ++ + // CraftBukkit start + this.displayName = this.getName(); + this.canPickUpLoot = true; +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 254953c1d8ad80173bcc9ed703bacaf32ca89c9a..7dea5e783ce2a1f8ddd2b3ab7a19e03a56c36ba1 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -46,9 +46,9 @@ public class PlayerChunk { + private static final List CHUNK_STATUSES = ChunkStatus.a(); + private static final PlayerChunk.State[] CHUNK_STATES = PlayerChunk.State.values(); + private final AtomicReferenceArray>> statusFutures; +- private volatile CompletableFuture> fullChunkFuture; +- private volatile CompletableFuture> tickingFuture; +- private volatile CompletableFuture> entityTickingFuture; ++ private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage ++ private volatile CompletableFuture> tickingFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage ++ private volatile CompletableFuture> entityTickingFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage + private CompletableFuture chunkSave; + public int oldTicketLevel; + private int ticketLevel; +@@ -64,6 +64,8 @@ public class PlayerChunk { + private boolean hasBeenLoaded; + private boolean x; + ++ private final PlayerChunkMap chunkMap; // Paper ++ + public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { + this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); + this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; +@@ -79,10 +81,49 @@ public class PlayerChunk { + this.ticketLevel = this.oldTicketLevel; + this.n = this.oldTicketLevel; + this.a(i); ++ this.chunkMap = (PlayerChunkMap)playerchunk_d; // Paper ++ } ++ ++ // Paper start ++ @Nullable ++ public final Chunk getEntityTickingChunk() { ++ CompletableFuture> completablefuture = this.entityTickingFuture; ++ Either either = completablefuture.getNow(null); ++ ++ return either == null ? null : either.left().orElse(null); ++ } ++ ++ @Nullable ++ public final Chunk getTickingChunk() { ++ CompletableFuture> completablefuture = this.tickingFuture; ++ Either either = completablefuture.getNow(null); ++ ++ return either == null ? null : either.left().orElse(null); ++ } ++ ++ @Nullable ++ public final Chunk getFullReadyChunk() { ++ CompletableFuture> completablefuture = this.fullChunkFuture; ++ Either either = completablefuture.getNow(null); ++ ++ return either == null ? null : either.left().orElse(null); ++ } ++ ++ public final boolean isEntityTickingReady() { ++ return this.isEntityTickingReady; ++ } ++ ++ public final boolean isTickingReady() { ++ return this.isTickingReady; ++ } ++ ++ public final boolean isFullChunkReady() { ++ return this.isFullChunkReady; + } ++ // Paper end + + // CraftBukkit start +- public Chunk getFullChunk() { ++ public final Chunk getFullChunk() { // Paper - final for inline + if (!getChunkState(this.oldTicketLevel).isAtLeast(PlayerChunk.State.BORDER)) return null; // note: using oldTicketLevel for isLoaded checks + return this.getFullChunkUnchecked(); + } +@@ -93,6 +134,14 @@ public class PlayerChunk { + return (either == null) ? null : (Chunk) either.left().orElse(null); + } + // CraftBukkit end ++ // Paper start - "real" get full chunk immediately ++ public final Chunk getFullChunkIfCached() { ++ // Note: Copied from above without ticket level check ++ CompletableFuture> statusFuture = this.getStatusFutureUnchecked(ChunkStatus.FULL); ++ Either either = (Either) statusFuture.getNow(null); ++ return either == null ? null : (Chunk) either.left().orElse(null); ++ } ++ // Paper end + + public CompletableFuture> getStatusFutureUnchecked(ChunkStatus chunkstatus) { + CompletableFuture> completablefuture = (CompletableFuture) this.statusFutures.get(chunkstatus.c()); +@@ -104,20 +153,23 @@ public class PlayerChunk { + return getChunkStatus(this.ticketLevel).b(chunkstatus) ? this.getStatusFutureUnchecked(chunkstatus) : PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE; + } + +- public CompletableFuture> a() { ++ public final CompletableFuture> getTickingFuture() { return this.a(); } // Paper - OBFHELPER ++ public final CompletableFuture> a() { // Paper - final for inline + return this.tickingFuture; + } + +- public CompletableFuture> b() { ++ public final CompletableFuture> getEntityTickingFuture() { return this.b(); } // Paper - OBFHELPER ++ public final CompletableFuture> b() { // Paper - final for inline + return this.entityTickingFuture; + } + +- public CompletableFuture> c() { ++ public final CompletableFuture> getFullChunkFuture() { return this.c(); } // Paper - OBFHELPER ++ public final CompletableFuture> c() { // Paper - final for inline + return this.fullChunkFuture; + } + + @Nullable +- public Chunk getChunk() { ++ public final Chunk getChunk() { // Paper - final for inline + CompletableFuture> completablefuture = this.a(); + Either either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error + +@@ -142,7 +194,7 @@ public class PlayerChunk { + return null; + } + +- public CompletableFuture getChunkSave() { ++ public final CompletableFuture getChunkSave() { // Paper - final for inline + return this.chunkSave; + } + +@@ -283,11 +335,11 @@ public class PlayerChunk { + }); + } + +- public ChunkCoordIntPair i() { ++ public final ChunkCoordIntPair i() { // Paper - final for inline + return this.location; + } + +- public int getTicketLevel() { ++ public final int getTicketLevel() { // Paper - final for inline + return this.ticketLevel; + } + +@@ -358,13 +410,27 @@ public class PlayerChunk { + + this.hasBeenLoaded |= flag3; + if (!flag2 && flag3) { +- this.fullChunkFuture = playerchunkmap.b(this); ++ // Paper start - cache ticking ready status ++ int expectCreateCount = ++this.fullChunkCreateCount; ++ this.fullChunkFuture = playerchunkmap.b(this); this.fullChunkFuture.thenAccept((either) -> { ++ if (either.left().isPresent() && PlayerChunk.this.fullChunkCreateCount == expectCreateCount) { ++ // note: Here is a very good place to add callbacks to logic waiting on this. ++ Chunk fullChunk = either.left().get(); ++ PlayerChunk.this.isFullChunkReady = true; ++ fullChunk.playerChunk = PlayerChunk.this; ++ ++ ++ } ++ }); ++ // Paper end + this.a(this.fullChunkFuture); + } + + if (flag2 && !flag3) { + completablefuture = this.fullChunkFuture; + this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; ++ ++this.fullChunkCreateCount; // Paper - cache ticking ready status ++ this.isFullChunkReady = false; // Paper - cache ticking ready status + this.a(((CompletableFuture>) completablefuture).thenApply((either1) -> { // CraftBukkit - decompile error + playerchunkmap.getClass(); + return either1.ifLeft(playerchunkmap::a); +@@ -375,12 +441,24 @@ public class PlayerChunk { + boolean flag5 = playerchunk_state1.isAtLeast(PlayerChunk.State.TICKING); + + if (!flag4 && flag5) { +- this.tickingFuture = playerchunkmap.a(this); ++ // Paper start - cache ticking ready status ++ this.tickingFuture = playerchunkmap.a(this); this.tickingFuture.thenAccept((either) -> { ++ if (either.left().isPresent()) { ++ // note: Here is a very good place to add callbacks to logic waiting on this. ++ Chunk tickingChunk = either.left().get(); ++ PlayerChunk.this.isTickingReady = true; ++ ++ ++ ++ ++ } ++ }); ++ // Paper end + this.a(this.tickingFuture); + } + + if (flag4 && !flag5) { +- this.tickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); ++ this.tickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage + this.tickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; + } + +@@ -392,12 +470,24 @@ public class PlayerChunk { + throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException())); + } + +- this.entityTickingFuture = playerchunkmap.b(this.location); ++ // Paper start - cache ticking ready status ++ this.entityTickingFuture = playerchunkmap.b(this.location); this.entityTickingFuture.thenAccept((either) -> { ++ if (either.left().isPresent()) { ++ // note: Here is a very good place to add callbacks to logic waiting on this. ++ Chunk entityTickingChunk = either.left().get(); ++ PlayerChunk.this.isEntityTickingReady = true; ++ ++ ++ ++ ++ } ++ }); ++ // Paper end + this.a(this.entityTickingFuture); + } + + if (flag6 && !flag7) { +- this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); ++ this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage + this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; + } + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index b9a0b37ceeeda9f6b1c4c23283e53dcb229f7249..0bf95b97140a67682ec2953ccc773f6faad7b7da 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -54,6 +54,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutLightUpdate; + import net.minecraft.network.protocol.game.PacketPlayOutMapChunk; + import net.minecraft.network.protocol.game.PacketPlayOutMount; + import net.minecraft.network.protocol.game.PacketPlayOutViewCentre; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.progress.WorldLoadListener; + import net.minecraft.util.CSVWriter; + import net.minecraft.util.EntitySlice; +@@ -146,6 +147,26 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + }; + // CraftBukkit end + ++ // Paper start - distance maps ++ private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); ++ ++ void addPlayerToDistanceMaps(EntityPlayer player) { ++ int chunkX = MCUtil.getChunkCoordinate(player.locX()); ++ int chunkZ = MCUtil.getChunkCoordinate(player.locZ()); ++ // Note: players need to be explicitly added to distance maps before they can be updated ++ } ++ ++ void removePlayerFromDistanceMaps(EntityPlayer player) { ++ ++ } ++ ++ void updateMaps(EntityPlayer player) { ++ int chunkX = MCUtil.getChunkCoordinate(player.locX()); ++ int chunkZ = MCUtil.getChunkCoordinate(player.locZ()); ++ // Note: players need to be explicitly added to distance maps before they can be updated ++ } ++ // Paper end ++ + public PlayerChunkMap(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier supplier, int i, boolean flag) { + super(new File(convertable_conversionsession.a(worldserver.getDimensionKey()), "region"), datafixer, flag); + this.visibleChunks = this.updatingChunks.clone(); +@@ -235,6 +256,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + }; + } + ++ // Paper start ++ public final int getEffectiveViewDistance() { ++ // TODO this needs to be checked on update ++ // Mojang currently sets it to +1 of the configured view distance. So subtract one to get the one we really want. ++ return this.viewDistance - 1; ++ } ++ // Paper end ++ + private CompletableFuture, PlayerChunk.Failure>> a(ChunkCoordIntPair chunkcoordintpair, int i, IntFunction intfunction) { + List>> list = Lists.newArrayList(); + int j = chunkcoordintpair.x; +@@ -945,6 +974,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + if (!flag1) { + this.chunkDistanceManager.a(SectionPosition.a((Entity) entityplayer), entityplayer); + } ++ this.addPlayerToDistanceMaps(entityplayer); // Paper - distance maps + } else { + SectionPosition sectionposition = entityplayer.O(); + +@@ -952,6 +982,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + if (!flag2) { + this.chunkDistanceManager.b(sectionposition, entityplayer); + } ++ this.removePlayerFromDistanceMaps(entityplayer); // Paper - distance maps + } + + for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) { +@@ -1062,6 +1093,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + } + ++ this.updateMaps(entityplayer); // Paper - distance maps ++ + } + + @Override +diff --git a/src/main/java/net/minecraft/server/level/RegionLimitedWorldAccess.java b/src/main/java/net/minecraft/server/level/RegionLimitedWorldAccess.java +index 91d9e6b554964e1f4dd67deea220a738f2715aee..04006caeeb42b523d986efc313828557854718d7 100644 +--- a/src/main/java/net/minecraft/server/level/RegionLimitedWorldAccess.java ++++ b/src/main/java/net/minecraft/server/level/RegionLimitedWorldAccess.java +@@ -141,6 +141,26 @@ public class RegionLimitedWorldAccess implements GeneratorAccessSeed { + return i >= this.n.x && i <= this.o.x && j >= this.n.z && j <= this.o.z; + } + ++ // Paper start - if loaded util ++ @Nullable ++ @Override ++ public IChunkAccess getChunkIfLoadedImmediately(int x, int z) { ++ return this.getChunkAt(x, z, ChunkStatus.FULL, false); ++ } ++ ++ @Override ++ public IBlockData getTypeIfLoaded(BlockPosition blockposition) { ++ IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getType(blockposition); ++ } ++ ++ @Override ++ public Fluid getFluidIfLoaded(BlockPosition blockposition) { ++ IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getFluid(blockposition); ++ } ++ // Paper end ++ + @Override + public IBlockData getType(BlockPosition blockposition) { + return this.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4).getType(blockposition); +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 9cae7fed34df3ff81d75105b2fcbc4510f2a0e71..285a03b57431bd6a4d26bb84e916d2c6e1eb0213 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -25,6 +25,7 @@ public class TicketType { + public static final TicketType UNKNOWN = a("unknown", Comparator.comparingLong(ChunkCoordIntPair::pair), 1); + public static final TicketType PLUGIN = a("plugin", (a, b) -> 0); // CraftBukkit + public static final TicketType PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit ++ public static final TicketType FUTURE_AWAIT = a("future_await", Long::compareTo); // Paper + + public static TicketType a(String s, Comparator comparator) { + return new TicketType<>(s, comparator, 0L); +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 634f34b8b88a1ef86cebf3126da9989a88707711..da53050c6c3d0a6ef3d5b96a88517e694536a268 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -12,6 +12,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; + import it.unimi.dsi.fastutil.longs.LongSet; + import it.unimi.dsi.fastutil.longs.LongSets; + import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2IntMap; + import it.unimi.dsi.fastutil.objects.ObjectIterator; + import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; + import java.io.BufferedWriter; +@@ -169,7 +170,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + private final Map entitiesByUUID = Maps.newHashMap(); + private final Queue entitiesToAdd = Queues.newArrayDeque(); + private final List players = Lists.newArrayList(); +- private final ChunkProviderServer chunkProvider; ++ public final ChunkProviderServer chunkProvider; // Paper - public + boolean tickingEntities; + private final MinecraftServer server; + public final WorldDataServer worldDataServer; // CraftBukkit - type +@@ -1689,7 +1690,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + ObjectIterator objectiterator = spawnercreature_d.b().object2IntEntrySet().iterator(); + + while (objectiterator.hasNext()) { +- it.unimi.dsi.fastutil.objects.Object2IntMap.Entry it_unimi_dsi_fastutil_objects_object2intmap_entry = (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry) objectiterator.next(); ++ Object2IntMap.Entry it_unimi_dsi_fastutil_objects_object2intmap_entry = (Object2IntMap.Entry) objectiterator.next(); // Paper - decompile fix + + bufferedwriter.write(String.format("spawn_count.%s: %d\n", ((EnumCreatureType) it_unimi_dsi_fastutil_objects_object2intmap_entry.getKey()).b(), it_unimi_dsi_fastutil_objects_object2intmap_entry.getIntValue())); + } +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 1a8f4ed15f7df5c734efc83c91444a42041af978..bc9321e77c6b2f9ac17821413fa1357da2f26b05 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -220,9 +220,9 @@ public class PlayerConnection implements PacketListenerPlayIn { + private final MinecraftServer minecraftServer; + public EntityPlayer player; + private int e; +- private long lastKeepAlive; +- private boolean awaitingKeepAlive; +- private long h; ++ private long lastKeepAlive; private void setLastPing(long lastPing) { this.lastKeepAlive = lastPing;}; private long getLastPing() { return this.lastKeepAlive;}; // Paper - OBFHELPER ++ private boolean awaitingKeepAlive; private void setPendingPing(boolean isPending) { this.awaitingKeepAlive = isPending;}; private boolean isPendingPing() { return this.awaitingKeepAlive;}; // Paper - OBFHELPER ++ private long h; private void setKeepAliveID(long keepAliveID) { this.h = keepAliveID;}; private long getKeepAliveID() {return this.h; }; // Paper - OBFHELPER + // CraftBukkit start - multithreaded fields + private volatile int chatThrottle; + private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(PlayerConnection.class, "chatThrottle"); +diff --git a/src/main/java/net/minecraft/util/DataBits.java b/src/main/java/net/minecraft/util/DataBits.java +index 54974a941a334dc0c8e62ffb8ca094772888b8fa..0c0576c8730069fb5364d8383dec8ab7e698658d 100644 +--- a/src/main/java/net/minecraft/util/DataBits.java ++++ b/src/main/java/net/minecraft/util/DataBits.java +@@ -84,6 +84,7 @@ public class DataBits { + return (int) (k >> l & this.d); + } + ++ public final long[] getDataBits() { return this.a(); } // Paper - OBFHELPER + public long[] a() { + return this.b; + } +diff --git a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +index 2b2c03ab62816f3d21ef953c4a45f55e3997cca6..e5641f2b41d89a57285fc072a48b951aa03a14a7 100644 +--- a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java ++++ b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +@@ -68,6 +68,15 @@ public abstract class IAsyncTaskHandler implements Mailbox entitytypes, World world) { + super(entitytypes, world); + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +index b91bb7f562d5c43ec1d0d88ba417e43b07dbf0f3..dbfcdc3cc7c1dccf785f5e13634e84c5af088985 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java ++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +@@ -225,6 +225,7 @@ public abstract class EntityInsentient extends EntityLiving { + return this.goalTarget; + } + ++ public org.bukkit.craftbukkit.entity.CraftMob getBukkitMob() { return (org.bukkit.craftbukkit.entity.CraftMob) super.getBukkitEntity(); } // Paper + public void setGoalTarget(@Nullable EntityLiving entityliving) { + // CraftBukkit start - fire event + setGoalTarget(entityliving, EntityTargetEvent.TargetReason.UNKNOWN, true); +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 97591280da3bfb30703a95fbe4e34b56b70544f4..c76ab1e6a54399eddae1ef2a595778385cd50026 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -231,6 +231,7 @@ public abstract class EntityLiving extends Entity { + public boolean collides = true; + public Set collidableExemptions = new HashSet<>(); + public boolean canPickUpLoot; ++ public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper + + @Override + public float getBukkitYaw() { +diff --git a/src/main/java/net/minecraft/world/entity/EntityTypes.java b/src/main/java/net/minecraft/world/entity/EntityTypes.java +index a32bc63ff1960bdb874d546ee42633063834da24..ac57ab9992e141c91cf48f033148ad78433b364c 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityTypes.java ++++ b/src/main/java/net/minecraft/world/entity/EntityTypes.java +@@ -3,6 +3,7 @@ package net.minecraft.world.entity; + import com.google.common.collect.ImmutableSet; + import java.util.Optional; + import java.util.Set; // Paper ++import java.util.Map; // Paper + import java.util.UUID; + import java.util.function.Function; + import java.util.stream.Stream; +@@ -441,8 +442,8 @@ public class EntityTypes { + return this.br.height; + } + +- @Nullable +- public T a(World world) { ++ public T create(World world) { return this.a(world); } // Paper - OBFHELPER ++ @Nullable public T a(World world) { // Paper - OBFHELPER + return this.bf.create(this, world); + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityMonster.java b/src/main/java/net/minecraft/world/entity/monster/EntityMonster.java +index acebee991eca1e19fc1094718dc40822b66756e1..c484e27650364b6537fe6b2e8e14de98382b86a3 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityMonster.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityMonster.java +@@ -27,6 +27,7 @@ import net.minecraft.world.level.WorldAccess; + + public abstract class EntityMonster extends EntityCreature implements IMonster { + ++ public org.bukkit.craftbukkit.entity.CraftMonster getBukkitMonster() { return (org.bukkit.craftbukkit.entity.CraftMonster) super.getBukkitEntity(); } // Paper + protected EntityMonster(EntityTypes entitytypes, World world) { + super(entitytypes, world); + this.f = 5; +diff --git a/src/main/java/net/minecraft/world/entity/player/PlayerInventory.java b/src/main/java/net/minecraft/world/entity/player/PlayerInventory.java +index 97db41acf626eec3e587964d0e73c370e5695bf0..2df3ae0b72ccb5f816d55fed15396ba5a1affb7f 100644 +--- a/src/main/java/net/minecraft/world/entity/player/PlayerInventory.java ++++ b/src/main/java/net/minecraft/world/entity/player/PlayerInventory.java +@@ -38,7 +38,7 @@ public class PlayerInventory implements IInventory, INamableTileEntity { + public final NonNullList items; + public final NonNullList armor; + public final NonNullList extraSlots; +- private final List> f; ++ private final List> f; public final List> getComponents() { return f; } // Paper - OBFHELPER + public int itemInHandIndex; + public final EntityHuman player; + private ItemStack carried; +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index dee2714cd9fc930a1a13e97d752ab2df39cd31ed..4010152dccc93019f2e7f284d80b92bae0d91c34 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -103,7 +103,7 @@ public final class ItemStack { + })).apply(instance, ItemStack::new); + }); + private static final Logger LOGGER = LogManager.getLogger(); +- public static final ItemStack b = new ItemStack((Item) null); ++ public static final ItemStack b = new ItemStack((Item) null);public static final ItemStack NULL_ITEM = b; // Paper - OBFHELPER + public static final DecimalFormat c = (DecimalFormat) SystemUtils.a((new DecimalFormat("#.##")), (decimalformat) -> { // CraftBukkit - decompile error + decimalformat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)); + }); +@@ -660,6 +660,24 @@ public final class ItemStack { + return this.tag != null ? this.tag.getList("Enchantments", 10) : new NBTTagList(); + } + ++ // Paper start - (this is just a good no conflict location) ++ public org.bukkit.inventory.ItemStack asBukkitMirror() { ++ return CraftItemStack.asCraftMirror(this); ++ } ++ public org.bukkit.inventory.ItemStack asBukkitCopy() { ++ return CraftItemStack.asCraftMirror(this.cloneItemStack()); ++ } ++ public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) { ++ return CraftItemStack.asNMSCopy(itemstack); ++ } ++ private org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack; ++ public org.bukkit.inventory.ItemStack getBukkitStack() { ++ if (bukkitStack == null || bukkitStack.getHandle() != this) { ++ bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this); ++ } ++ return bukkitStack; ++ } ++ // Paper end + public void setTag(@Nullable NBTTagCompound nbttagcompound) { + this.tag = nbttagcompound; + if (this.getItem().usesDurability()) { +@@ -758,6 +776,7 @@ public final class ItemStack { + return this.tag != null && this.tag.hasKeyOfType("Enchantments", 9) ? !this.tag.getList("Enchantments", 10).isEmpty() : false; + } + ++ public void getOrCreateTagAndSet(String s, NBTBase nbtbase) { a(s, nbtbase);} // Paper - OBFHELPER + public void a(String s, NBTBase nbtbase) { + this.getOrCreateTag().set(s, nbtbase); + } +@@ -843,6 +862,7 @@ public final class ItemStack { + // CraftBukkit start + @Deprecated + public void setItem(Item item) { ++ this.bukkitStack = null; // Paper + this.item = item; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/item/alchemy/PotionUtil.java b/src/main/java/net/minecraft/world/item/alchemy/PotionUtil.java +index daad63e731008eddccd3f51418a2a9b2d587f77b..795bc60a73e1e628590803fd515ffb78302d4f97 100644 +--- a/src/main/java/net/minecraft/world/item/alchemy/PotionUtil.java ++++ b/src/main/java/net/minecraft/world/item/alchemy/PotionUtil.java +@@ -121,6 +121,7 @@ public class PotionUtil { + return nbttagcompound == null ? Potions.EMPTY : PotionRegistry.a(nbttagcompound.getString("Potion")); + } + ++ public static ItemStack addPotionToItemStack(ItemStack itemstack, PotionRegistry potionregistry) { return a(itemstack, potionregistry); } // Paper - OBFHELPER + public static ItemStack a(ItemStack itemstack, PotionRegistry potionregistry) { + MinecraftKey minecraftkey = IRegistry.POTION.getKey(potionregistry); + +diff --git a/src/main/java/net/minecraft/world/level/BlockAccessAir.java b/src/main/java/net/minecraft/world/level/BlockAccessAir.java +index 5f8022745f709b6d542182d2ac94147aefdd3f0f..543b13c1e43135c044f834c2a6231e174536b623 100644 +--- a/src/main/java/net/minecraft/world/level/BlockAccessAir.java ++++ b/src/main/java/net/minecraft/world/level/BlockAccessAir.java +@@ -20,6 +20,18 @@ public enum BlockAccessAir implements IBlockAccess { + return null; + } + ++ // Paper start - If loaded util ++ @Override ++ public Fluid getFluidIfLoaded(BlockPosition blockposition) { ++ return this.getFluid(blockposition); ++ } ++ ++ @Override ++ public IBlockData getTypeIfLoaded(BlockPosition blockposition) { ++ return this.getType(blockposition); ++ } ++ // Paper end ++ + @Override + public IBlockData getType(BlockPosition blockposition) { + return Blocks.AIR.getBlockData(); +diff --git a/src/main/java/net/minecraft/world/level/ChunkCache.java b/src/main/java/net/minecraft/world/level/ChunkCache.java +index 8541e87a34612e8bc86cf5c291164e091641d1af..7a760ef0264c9041c38bdfb8fd31333052c26139 100644 +--- a/src/main/java/net/minecraft/world/level/ChunkCache.java ++++ b/src/main/java/net/minecraft/world/level/ChunkCache.java +@@ -4,6 +4,7 @@ import java.util.function.Predicate; + import java.util.stream.Stream; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPosition; ++import net.minecraft.server.level.WorldServer; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.entity.TileEntity; +@@ -23,7 +24,7 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess { + protected final int b; + protected final IChunkAccess[][] c; + protected boolean d; +- protected final World e; ++ protected final World e; protected final World getWorld() { return e; } // Paper - OBFHELPER + + public ChunkCache(World world, BlockPosition blockposition, BlockPosition blockposition1) { + this.e = world; +@@ -42,7 +43,7 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess { + + for (k = this.a; k <= i; ++k) { + for (l = this.b; l <= j; ++l) { +- this.c[k - this.a][l - this.b] = ichunkprovider.a(k, l); ++ this.c[k - this.a][l - this.b] = ((WorldServer)world).getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(k, l); // Paper + } + } + +@@ -67,7 +68,7 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess { + int k = i - this.a; + int l = j - this.b; + +- if (k >= 0 && k < this.c.length && l >= 0 && l < this.c[k].length) { ++ if (k >= 0 && k < this.c.length && l >= 0 && l < this.c[k].length) { // Paper - if this changes, update getChunkIfLoaded below + IChunkAccess ichunkaccess = this.c[k][l]; + + return (IChunkAccess) (ichunkaccess != null ? ichunkaccess : new ChunkEmpty(this.e, new ChunkCoordIntPair(i, j))); +@@ -86,6 +87,29 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess { + return this.a(i, j); + } + ++ // Paper start - if loaded util ++ private IChunkAccess getChunkIfLoaded(int x, int z) { ++ int k = x - this.a; ++ int l = z - this.b; ++ ++ if (k >= 0 && k < this.c.length && l >= 0 && l < this.c[k].length) { ++ return this.c[k][l]; ++ } ++ return null; ++ } ++ @Override ++ public Fluid getFluidIfLoaded(BlockPosition blockposition) { ++ IChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getFluid(blockposition); ++ } ++ ++ @Override ++ public IBlockData getTypeIfLoaded(BlockPosition blockposition) { ++ IChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getType(blockposition); ++ } ++ // Paper end ++ + @Nullable + @Override + public TileEntity getTileEntity(BlockPosition blockposition) { +diff --git a/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java b/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java +index 14e55bf842e928d1e8e2137f9efdef0f7c336362..9a88791be443a5b18934e7d752aee6dcdb8aa38f 100644 +--- a/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java ++++ b/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java +@@ -12,27 +12,32 @@ public class ChunkCoordIntPair { + public static final long a = pair(1875016, 1875016); + public final int x; + public final int z; ++ public final long longKey; // Paper + + public ChunkCoordIntPair(int i, int j) { + this.x = i; + this.z = j; ++ this.longKey = pair(this.x, this.z); // Paper + } + + public ChunkCoordIntPair(BlockPosition blockposition) { + this.x = blockposition.getX() >> 4; + this.z = blockposition.getZ() >> 4; ++ this.longKey = pair(this.x, this.z); // Paper + } + + public ChunkCoordIntPair(long i) { + this.x = (int) i; + this.z = (int) (i >> 32); ++ this.longKey = pair(this.x, this.z); // Paper + } + + public long pair() { +- return pair(this.x, this.z); ++ return longKey; // Paper + } + +- public static long pair(int i, int j) { ++ public static long pair(final BlockPosition pos) { return pair(pos.getX() >> 4, pos.getZ() >> 4); } // Paper - OBFHELPER ++ public static long pair(int i, int j) { + return (long) i & 4294967295L | ((long) j & 4294967295L) << 32; + } + +diff --git a/src/main/java/net/minecraft/world/level/IBlockAccess.java b/src/main/java/net/minecraft/world/level/IBlockAccess.java +index 25e50b57f42dde156443480d73c6c9985df6f0c6..e799765ecfada1eec78beb71651e52ad355a30aa 100644 +--- a/src/main/java/net/minecraft/world/level/IBlockAccess.java ++++ b/src/main/java/net/minecraft/world/level/IBlockAccess.java +@@ -8,9 +8,11 @@ import javax.annotation.Nullable; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; + import net.minecraft.util.MathHelper; ++import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.entity.TileEntity; + import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.level.material.Material; + import net.minecraft.world.phys.AxisAlignedBB; + import net.minecraft.world.phys.MovingObjectPositionBlock; + import net.minecraft.world.phys.Vec3D; +@@ -22,6 +24,19 @@ public interface IBlockAccess { + TileEntity getTileEntity(BlockPosition blockposition); + + IBlockData getType(BlockPosition blockposition); ++ // Paper start - if loaded util ++ IBlockData getTypeIfLoaded(BlockPosition blockposition); ++ default Material getMaterialIfLoaded(BlockPosition blockposition) { ++ IBlockData type = this.getTypeIfLoaded(blockposition); ++ return type == null ? null : type.getMaterial(); ++ } ++ ++ default Block getBlockIfLoaded(BlockPosition blockposition) { ++ IBlockData type = this.getTypeIfLoaded(blockposition); ++ return type == null ? null : type.getBlock(); ++ } ++ Fluid getFluidIfLoaded(BlockPosition blockposition); ++ // Paper end + + Fluid getFluid(BlockPosition blockposition); + +diff --git a/src/main/java/net/minecraft/world/level/IWorldReader.java b/src/main/java/net/minecraft/world/level/IWorldReader.java +index d3d33e77ce09d485552076c5ab6faf08a16d90db..5f12b290d59e0b5e843d644bf7f608a946ef02c0 100644 +--- a/src/main/java/net/minecraft/world/level/IWorldReader.java ++++ b/src/main/java/net/minecraft/world/level/IWorldReader.java +@@ -18,6 +18,7 @@ import net.minecraft.world.phys.AxisAlignedBB; + + public interface IWorldReader extends IBlockLightAccess, ICollisionAccess, BiomeManager.Provider { + ++ @Nullable IChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading) + @Nullable + IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag); + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 323149594e47ee4c999befe89210f2dc1e3de860..f8df4f007a5bc24303b7278d734ab8890c0dd7bb 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -74,6 +74,7 @@ import org.bukkit.craftbukkit.SpigotTimings; // Spigot + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.block.CapturedBlockState; ++import org.bukkit.craftbukkit.block.CraftBlockState; + import org.bukkit.craftbukkit.block.data.CraftBlockData; + import org.bukkit.event.block.BlockPhysicsEvent; + // CraftBukkit end +@@ -255,17 +256,50 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return i < 0 || i >= 256; + } + +- public Chunk getChunkAtWorldCoords(BlockPosition blockposition) { ++ public final Chunk getChunkAtWorldCoords(BlockPosition blockposition) { // Paper - help inline + return this.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4); + } + + @Override +- public Chunk getChunkAt(int i, int j) { +- return (Chunk) this.getChunkAt(i, j, ChunkStatus.FULL); ++ public final Chunk getChunkAt(int i, int j) { // Paper - final to help inline ++ return (Chunk) this.getChunkAt(i, j, ChunkStatus.FULL, true); // Paper - avoid a method jump ++ } ++ ++ // Paper start - if loaded ++ @Nullable ++ @Override ++ public final IChunkAccess getChunkIfLoadedImmediately(int x, int z) { ++ return ((WorldServer)this).chunkProvider.getChunkAtIfLoadedImmediately(x, z); + } + + @Override +- public IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag) { ++ public final IBlockData getTypeIfLoaded(BlockPosition blockposition) { ++ // CraftBukkit start - tree generation ++ if (captureTreeGeneration) { ++ CraftBlockState previous = capturedBlockStates.get(blockposition); ++ if (previous != null) { ++ return previous.getHandle(); ++ } ++ } ++ // CraftBukkit end ++ if (!isValidLocation(blockposition)) { ++ return Blocks.AIR.getBlockData(); ++ } ++ IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ ++ return chunk == null ? null : chunk.getType(blockposition); ++ } ++ ++ @Override ++ public Fluid getFluidIfLoaded(BlockPosition blockposition) { ++ IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ ++ return chunk == null ? null : chunk.getFluid(blockposition); ++ } ++ // Paper end ++ ++ @Override ++ public final IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag) { // Paper - final for inline + IChunkAccess ichunkaccess = this.getChunkProvider().getChunkAt(i, j, chunkstatus, flag); + + if (ichunkaccess == null && flag) { +@@ -276,7 +310,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + } + + @Override +- public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) { ++ public final boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) { // Paper - final for inline + return this.a(blockposition, iblockdata, i, 512); + } + +@@ -422,8 +456,9 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + public void a(BlockPosition blockposition, IBlockData iblockdata, IBlockData iblockdata1) {} + +- @Override +- public boolean a(BlockPosition blockposition, boolean flag) { ++ public boolean setAir(BlockPosition blockposition) { return this.a(blockposition, false); } // Paper - OBFHELPER ++ public boolean setAir(BlockPosition blockposition, boolean moved) { return this.a(blockposition, moved); } // Paper - OBFHELPER ++ @Override public boolean a(BlockPosition blockposition, boolean flag) { // Paper - OBFHELPER + Fluid fluid = this.getFluid(blockposition); + + return this.setTypeAndData(blockposition, fluid.getBlockData(), 3 | (flag ? 64 : 0)); +@@ -569,7 +604,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + if (isOutsideWorld(blockposition)) { + return Blocks.VOID_AIR.getBlockData(); + } else { +- Chunk chunk = this.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ Chunk chunk = (Chunk) this.getChunkProvider().getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4, ChunkStatus.FULL, true); // Paper - manually inline to reduce hops and avoid unnecessary null check to reduce total byte code size, this should never return null and if it does we will see it the next line but the real stack trace will matter in the chunk engine + + return chunk.getType(blockposition); + } +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +index ef652d8f3eb7371c9ddfc3afe67fd1bd669a77c0..2902117fd2803741b053a04fda7f4414fb8593cb 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +@@ -688,6 +688,7 @@ public abstract class BlockBase { + return this.a != null ? this.a.d : Block.a(this.getCollisionShape(iblockaccess, blockposition)); + } + ++ public final IBlockData getBlockData() { return p(); } // Paper - OBFHELPER + protected abstract IBlockData p(); + + public boolean isRequiresSpecialTool() { +diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +index 79f645ff1b6274bbdf5dc3f96a762b3b63397c82..2bb03f1cb9671a7754a68059219f783d4508eeb9 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -47,6 +47,7 @@ public class WorldBorder { + return this.b(entity.locX(), entity.locZ()); + } + ++ public final VoxelShape asVoxelShape(){ return c();} // Paper - OBFHELPER + public VoxelShape c() { + return this.j.m(); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index 4561cf94fe1dce51abad0ac8635749ed87c8b307..b15200c2a3923bd8be2ee5e73fdadfeea3e3a8dc 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -60,7 +60,7 @@ public class Chunk implements IChunkAccess { + + private static final Logger LOGGER = LogManager.getLogger(); + @Nullable +- public static final ChunkSection a = null; ++ public static final ChunkSection a = null; public static final ChunkSection EMPTY_CHUNK_SECTION = a; // Paper - OBFHELPER + private final ChunkSection[] sections; + private BiomeStorage d; + private final Map e; +@@ -83,7 +83,7 @@ public class Chunk implements IChunkAccess { + private Supplier u; + @Nullable + private Consumer v; +- private final ChunkCoordIntPair loc; ++ private final ChunkCoordIntPair loc; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key + private volatile boolean x; + + public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage) { +@@ -100,7 +100,8 @@ public class Chunk implements IChunkAccess { + this.n = new ShortList[16]; + this.entitySlices = (List[]) (new List[16]); // Spigot + this.world = (WorldServer) world; // CraftBukkit - type +- this.loc = chunkcoordintpair; ++ this.locX = chunkcoordintpair.x; this.locZ = chunkcoordintpair.z; // Paper - reduce need for field look ups ++ this.loc = chunkcoordintpair; this.coordinateKey = ChunkCoordIntPair.pair(locX, locZ); // Paper - cache long key + this.i = chunkconverter; + HeightMap.Type[] aheightmap_type = HeightMap.Type.values(); + int j = aheightmap_type.length; +@@ -146,6 +147,110 @@ public class Chunk implements IChunkAccess { + public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(DATA_TYPE_REGISTRY); + // CraftBukkit end + ++ // Paper start ++ public final com.destroystokyo.paper.util.maplist.EntityList entities = new com.destroystokyo.paper.util.maplist.EntityList(); ++ public PlayerChunk playerChunk; ++ ++ static final int NEIGHBOUR_CACHE_RADIUS = 3; ++ public static int getNeighbourCacheRadius() { ++ return NEIGHBOUR_CACHE_RADIUS; ++ } ++ ++ boolean loadedTicketLevel; ++ private long neighbourChunksLoadedBitset; ++ private final Chunk[] loadedNeighbourChunks = new Chunk[(NEIGHBOUR_CACHE_RADIUS * 2 + 1) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)]; ++ ++ private static int getNeighbourIndex(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_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))); ++ } ++ ++ public final Chunk getRelativeNeighbourIfLoaded(final int relativeX, final int relativeZ) { ++ return this.loadedNeighbourChunks[getNeighbourIndex(relativeX, relativeZ)]; ++ } ++ ++ public final boolean isNeighbourLoaded(final int relativeX, final int relativeZ) { ++ return (this.neighbourChunksLoadedBitset & (1L << getNeighbourIndex(relativeX, relativeZ))) != 0; ++ } ++ ++ public final void setNeighbourLoaded(final int relativeX, final int relativeZ, final Chunk chunk) { ++ if (chunk == null) { ++ throw new IllegalArgumentException("Chunk must be non-null, neighbour: (" + relativeX + "," + relativeZ + "), chunk: " + this.loc); ++ } ++ final long before = this.neighbourChunksLoadedBitset; ++ final int index = getNeighbourIndex(relativeX, relativeZ); ++ this.loadedNeighbourChunks[index] = chunk; ++ this.neighbourChunksLoadedBitset |= (1L << index); ++ this.onNeighbourChange(before, this.neighbourChunksLoadedBitset); ++ } ++ ++ public final void setNeighbourUnloaded(final int relativeX, final int relativeZ) { ++ final long before = this.neighbourChunksLoadedBitset; ++ final int index = getNeighbourIndex(relativeX, relativeZ); ++ this.loadedNeighbourChunks[index] = null; ++ this.neighbourChunksLoadedBitset &= ~(1L << index); ++ this.onNeighbourChange(before, this.neighbourChunksLoadedBitset); ++ } ++ ++ public final void resetNeighbours() { ++ final long before = this.neighbourChunksLoadedBitset; ++ this.neighbourChunksLoadedBitset = 0L; ++ java.util.Arrays.fill(this.loadedNeighbourChunks, null); ++ this.onNeighbourChange(before, 0L); ++ } ++ ++ protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { ++ ++ } ++ ++ public final boolean isAnyNeighborsLoaded() { ++ return neighbourChunksLoadedBitset != 0; ++ } ++ public final boolean areNeighboursLoaded(final int radius) { ++ return Chunk.areNeighboursLoaded(this.neighbourChunksLoadedBitset, radius); ++ } ++ ++ public static boolean areNeighboursLoaded(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 << getNeighbourIndex(0, 0))) != 0; ++ } ++ case 1: { ++ long mask = 0L; ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ mask |= (1L << getNeighbourIndex(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 << getNeighbourIndex(dx, dz)); ++ } ++ } ++ return (bitset & mask) == mask; ++ } ++ case 3: { ++ long mask = 0L; ++ for (int dx = -3; dx <= 3; ++dx) { ++ for (int dz = -3; dz <= 3; ++dz) { ++ mask |= (1L << getNeighbourIndex(dx, dz)); ++ } ++ } ++ return (bitset & mask) == mask; ++ } ++ ++ default: ++ throw new IllegalArgumentException("Radius not recognized: " + radius); ++ } ++ } ++ // Paper end ++ + public Chunk(World world, ProtoChunk protochunk) { + this(world, protochunk.getPos(), protochunk.getBiomeIndex(), protochunk.p(), protochunk.n(), protochunk.o(), protochunk.getInhabitedTime(), protochunk.getSections(), (Consumer) null); + Iterator iterator = protochunk.y().iterator(); +@@ -251,6 +356,18 @@ public class Chunk implements IChunkAccess { + } + } + ++ // Paper start - If loaded util ++ @Override ++ public Fluid getFluidIfLoaded(BlockPosition blockposition) { ++ return this.getFluid(blockposition); ++ } ++ ++ @Override ++ public IBlockData getTypeIfLoaded(BlockPosition blockposition) { ++ return this.getType(blockposition); ++ } ++ // Paper end ++ + @Override + public Fluid getFluid(BlockPosition blockposition) { + return this.a(blockposition.getX(), blockposition.getY(), blockposition.getZ()); +@@ -391,6 +508,7 @@ public class Chunk implements IChunkAccess { + entity.chunkX = this.loc.x; + entity.chunkY = k; + entity.chunkZ = this.loc.z; ++ this.entities.add(entity); // Paper - per chunk entity list + this.entitySlices[k].add(entity); + } + +@@ -414,6 +532,7 @@ public class Chunk implements IChunkAccess { + } + + this.entitySlices[i].remove(entity); ++ this.entities.remove(entity); // Paper + } + + @Override +@@ -435,6 +554,7 @@ public class Chunk implements IChunkAccess { + return this.a(blockposition, Chunk.EnumTileEntityState.CHECK); + } + ++ @Nullable public final TileEntity getTileEntityImmediately(BlockPosition pos) { return this.a(pos, EnumTileEntityState.IMMEDIATE); } // Paper - OBFHELPER + @Nullable + public TileEntity a(BlockPosition blockposition, Chunk.EnumTileEntityState chunk_enumtileentitystate) { + // CraftBukkit start +@@ -546,7 +666,25 @@ public class Chunk implements IChunkAccess { + + // CraftBukkit start + public void loadCallback() { ++ // Paper start - neighbour cache ++ int chunkX = this.loc.x; ++ int chunkZ = this.loc.z; ++ ChunkProviderServer chunkProvider = ((WorldServer)this.world).getChunkProvider(); ++ for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { ++ for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { ++ Chunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); ++ if (neighbour != null) { ++ neighbour.setNeighbourLoaded(-dx, -dz, this); ++ // should be in cached already ++ this.setNeighbourLoaded(dx, dz, neighbour); ++ } ++ } ++ } ++ this.setNeighbourLoaded(0, 0, this); ++ this.loadedTicketLevel = true; ++ // Paper end - neighbour cache + org.bukkit.Server server = this.world.getServer(); ++ ((WorldServer)this.world).getChunkProvider().addLoadedChunk(this); // Paper + if (server != null) { + /* + * If it's a new world, the first few chunks are generated inside +@@ -585,6 +723,22 @@ public class Chunk implements IChunkAccess { + server.getPluginManager().callEvent(unloadEvent); + // note: saving can be prevented, but not forced if no saving is actually required + this.mustNotSave = !unloadEvent.isSaveChunk(); ++ ((WorldServer)this.world).getChunkProvider().removeLoadedChunk(this); // Paper ++ // Paper start - neighbour cache ++ int chunkX = this.loc.x; ++ int chunkZ = this.loc.z; ++ ChunkProviderServer chunkProvider = ((WorldServer)this.world).getChunkProvider(); ++ for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { ++ for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { ++ Chunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); ++ if (neighbour != null) { ++ neighbour.setNeighbourUnloaded(-dx, -dz); ++ } ++ } ++ } ++ this.loadedTicketLevel = false; ++ this.resetNeighbours(); ++ // Paper end + } + // CraftBukkit end + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +index cf2dd6da5ce88aafdcc4db63af18eda9396a066a..a4e2eb1a753e8fcb48982d78fe80e505bce5c476 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +@@ -139,6 +139,7 @@ public class ChunkSection { + return this.blockIds; + } + ++ public void writeChunkSection(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // Paper - OBFHELPER + public void b(PacketDataSerializer packetdataserializer) { + packetdataserializer.writeShort(this.nonEmptyBlockCount); + this.blockIds.b(packetdataserializer); +diff --git a/src/main/java/net/minecraft/world/level/chunk/DataPalette.java b/src/main/java/net/minecraft/world/level/chunk/DataPalette.java +index f1dd62541187d007a69087f0279508b6b18d5166..44fe0ee179eebfa6c4c0403a7f06735d7da5c773 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/DataPalette.java ++++ b/src/main/java/net/minecraft/world/level/chunk/DataPalette.java +@@ -7,10 +7,12 @@ import net.minecraft.network.PacketDataSerializer; + + public interface DataPalette { + ++ default int getOrCreateIdFor(T object) { return this.a(object); } // Paper - OBFHELPER + int a(T t0); + + boolean a(Predicate predicate); + ++ @Nullable default T getObject(int dataBits) { return this.a(dataBits); } // Paper - OBFHELPER + @Nullable + T a(int i); + +diff --git a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +index fe441146757a4ac0562d5b493fb6430e33b9ee28..e397b871b846c3a90bc75d0e1cf0683b6a3d0ca9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java ++++ b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +@@ -19,7 +19,7 @@ import net.minecraft.util.MathHelper; + + public class DataPaletteBlock implements DataPaletteExpandable { + +- private final DataPalette b; ++ private final DataPalette b; private final DataPalette getDataPaletteGlobal() { return this.b; } // Paper - OBFHELPER + private final DataPaletteExpandable c = (i, object) -> { + return 0; + }; +@@ -27,9 +27,9 @@ public class DataPaletteBlock implements DataPaletteExpandable { + private final Function e; + private final Function f; + private final T g; +- protected DataBits a; +- private DataPalette h; +- private int i; ++ protected DataBits a; public final DataBits getDataBits() { return this.a; } // Paper - OBFHELPER ++ private DataPalette h; private DataPalette getDataPalette() { return this.h; } // Paper - OBFHELPER ++ private int i; private int getBitsPerObject() { return this.i; } // Paper - OBFHELPER + private final ReentrantLock j = new ReentrantLock(); + + public void a() { +@@ -64,6 +64,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { + return j << 8 | k << 4 | i; + } + ++ private void initialize(int bitsPerObject) { this.b(bitsPerObject); } // Paper - OBFHELPER + private void b(int i) { + if (i != this.i) { + this.i = i; +@@ -141,6 +142,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { + return t0 == null ? this.g : t0; + } + ++ public void writeDataPaletteBlock(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // Paper - OBFHELPER + public void b(PacketDataSerializer packetdataserializer) { + this.a(); + packetdataserializer.writeByte(this.i); +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 4192d30ad2117a12a4058b48581d6cf93b50088c..7572ca53a5cca8ca5085d18c24048b85dda4daa9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -99,6 +99,18 @@ public class ProtoChunk implements IChunkAccess { + + } + ++ // Paper start - If loaded util ++ @Override ++ public Fluid getFluidIfLoaded(BlockPosition blockposition) { ++ return this.getFluid(blockposition); ++ } ++ ++ @Override ++ public IBlockData getTypeIfLoaded(BlockPosition blockposition) { ++ return this.getType(blockposition); ++ } ++ // Paper end ++ + @Override + public IBlockData getType(BlockPosition blockposition) { + int i = blockposition.getY(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java +index 859561a5dccba6548967b685b20e8fcfc296db2a..9ebf2806122a308f7655cdbee1f642cd80c9932c 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java +@@ -27,7 +27,7 @@ public class IOWorker implements AutoCloseable { + private static final Logger LOGGER = LogManager.getLogger(); + private final AtomicBoolean b = new AtomicBoolean(); + private final ThreadedMailbox c; +- private final RegionFileCache d; ++ private final RegionFileCache d;public RegionFileCache getRegionFileCache() { return d; } // Paper - OBFHELPER + private final Map e = Maps.newLinkedHashMap(); + + protected IOWorker(File file, boolean flag, String s) { +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 5e3bbc6d89794e23df7b60b13ae48c0f5136f20e..d1b761055c508a4b80436b50a832e00d0449d8cb 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 +@@ -112,6 +112,7 @@ public class RegionFile implements AutoCloseable { + return this.e.resolve(s); + } + ++ @Nullable public synchronized DataInputStream getReadStream(ChunkCoordIntPair chunkCoordIntPair) throws IOException { return a(chunkCoordIntPair);} // Paper - OBFHELPER + @Nullable + public synchronized DataInputStream a(ChunkCoordIntPair chunkcoordintpair) throws IOException { + int i = this.getOffset(chunkcoordintpair); +diff --git a/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java b/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java +index 633a484cebc99f4a2f071b7f84b0b63d0ec3f985..3941dd33da4b5c09d0087143f1d8a2d76fc18792 100644 +--- a/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java ++++ b/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java +@@ -194,10 +194,12 @@ public class AxisAlignedBB { + return this.d(vec3d.x, vec3d.y, vec3d.z); + } + ++ public final boolean intersects(AxisAlignedBB axisalignedbb) { return this.c(axisalignedbb); } // Paper - OBFHELPER + public boolean c(AxisAlignedBB axisalignedbb) { + return this.a(axisalignedbb.minX, axisalignedbb.minY, axisalignedbb.minZ, axisalignedbb.maxX, axisalignedbb.maxY, axisalignedbb.maxZ); + } + ++ public final boolean intersects(double d0, double d1, double d2, double d3, double d4, double d5) { return a(d0, d1, d2, d3, d4, d5); } // Paper - OBFHELPER + public boolean a(double d0, double d1, double d2, double d3, double d4, double d5) { + return this.minX < d3 && this.maxX > d0 && this.minY < d4 && this.maxY > d1 && this.minZ < d5 && this.maxZ > d2; + } +@@ -210,6 +212,7 @@ public class AxisAlignedBB { + return d0 >= this.minX && d0 < this.maxX && d1 >= this.minY && d1 < this.maxY && d2 >= this.minZ && d2 < this.maxZ; + } + ++ public final double getAverageSideLength(){return a();} // Paper - OBFHELPER + public double a() { + double d0 = this.b(); + double d1 = this.c(); +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +index 44d37272a337fee9606ebaa1b6f647c0fd392320..fdd9e37a8c90fc3311e515355af0a0593efbdacc 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +@@ -31,10 +31,12 @@ public final class VoxelShapes { + public static final VoxelShape a = create(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + private static final VoxelShape c = new VoxelShapeArray(new VoxelShapeBitSet(0, 0, 0), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D})); + ++ public static final VoxelShape empty() {return a();} // Paper - OBFHELPER + public static VoxelShape a() { + return VoxelShapes.c; + } + ++ public static final VoxelShape fullCube() {return b();} // Paper - OBFHELPER + public static VoxelShape b() { + return VoxelShapes.b; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 52fa8a38545be43a31363d1d57e42471bbb0c499..275b943a59ef28c831a068987e111e84ebba3bb7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -85,6 +85,7 @@ public final class CraftItemStack extends ItemStack { + } + + net.minecraft.world.item.ItemStack handle; ++ public net.minecraft.world.item.ItemStack getHandle() { return handle; } // Paper + + /** + * Mirror +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 9ad17c560c8d99a396543ab9f97c34de648f6544..4bf48f77f3f7cd62a91590543f5af441c8268029 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -43,6 +43,7 @@ import org.bukkit.scheduler.BukkitWorker; + */ + public class CraftScheduler implements BukkitScheduler { + ++ static Plugin MINECRAFT = new MinecraftInternalPlugin(); + /** + * Counter for IDs. Order doesn't matter, only uniqueness. + */ +@@ -177,6 +178,11 @@ public class CraftScheduler implements BukkitScheduler { + runTaskTimer(plugin, (Object) task, delay, period); + } + ++ public BukkitTask scheduleInternalTask(Runnable run, int delay, String taskName) { ++ final CraftTask task = new CraftTask(run, nextId(), taskName); ++ return handle(task, delay); ++ } ++ + public BukkitTask runTaskTimer(Plugin plugin, Object runnable, long delay, long period) { + validate(plugin, runnable); + if (delay < 0L) { +@@ -400,13 +406,20 @@ public class CraftScheduler implements BukkitScheduler { + task.run(); + task.timings.stopTiming(); // Spigot + } catch (final Throwable throwable) { +- task.getOwner().getLogger().log( ++ // Paper start ++ String msg = String.format( ++ "Task #%s for %s generated an exception", ++ task.getTaskId(), ++ task.getOwner().getDescription().getFullName()); ++ if (task.getOwner() == MINECRAFT) { ++ net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); ++ } else { ++ task.getOwner().getLogger().log( + Level.WARNING, +- String.format( +- "Task #%s for %s generated an exception", +- task.getTaskId(), +- task.getOwner().getDescription().getFullName()), ++ msg, + throwable); ++ } ++ // Paper end + } finally { + currentTask = null; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +index 3c7066192ea4c05c101404bb56cbc839771f4200..09aa6809c5400ce8548ac902908b750ce7c964ec 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +@@ -39,6 +39,21 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + CraftTask(final Object task) { + this(null, task, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING); + } ++ // Paper start ++ public String taskName = null; ++ boolean internal = false; ++ CraftTask(final Object task, int id, String taskName) { ++ this.rTask = (Runnable) task; ++ this.cTask = null; ++ this.plugin = CraftScheduler.MINECRAFT; ++ this.taskName = taskName; ++ this.internal = true; ++ this.id = id; ++ this.period = CraftTask.NO_REPEATING; ++ this.taskName = taskName; ++ this.timings = null; // Will be changed in later patch ++ } ++ // Paper end + + CraftTask(final Plugin plugin, final Object task, final int id, final long period) { + this.plugin = plugin; +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java +new file mode 100644 +index 0000000000000000000000000000000000000000..49dc0c441b9dd7e7745cf15ced67f383ebee1f99 +--- /dev/null ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java +@@ -0,0 +1,132 @@ ++package org.bukkit.craftbukkit.scheduler; ++ ++ ++import org.bukkit.Server; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.configuration.file.FileConfiguration; ++import org.bukkit.generator.ChunkGenerator; ++import org.bukkit.plugin.PluginBase; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.PluginLoader; ++import org.bukkit.plugin.PluginLogger; ++ ++import java.io.File; ++import java.io.InputStream; ++import java.util.List; ++ ++public class MinecraftInternalPlugin extends PluginBase { ++ private boolean enabled = true; ++ ++ private final String pluginName; ++ private PluginDescriptionFile pdf; ++ ++ public MinecraftInternalPlugin() { ++ this.pluginName = "Minecraft"; ++ pdf = new PluginDescriptionFile(pluginName, "1.0", "nms"); ++ } ++ ++ public void setEnabled(boolean enabled) { ++ this.enabled = enabled; ++ } ++ ++ @Override ++ public File getDataFolder() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public PluginDescriptionFile getDescription() { ++ return pdf; ++ } ++ ++ @Override ++ public FileConfiguration getConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public InputStream getResource(String filename) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void saveConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void saveDefaultConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void saveResource(String resourcePath, boolean replace) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void reloadConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public PluginLogger getLogger() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public PluginLoader getPluginLoader() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public Server getServer() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public boolean isEnabled() { ++ return enabled; ++ } ++ ++ @Override ++ public void onDisable() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void onLoad() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void onEnable() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public boolean isNaggable() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void setNaggable(boolean canNag) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +index f4ea5809f48fda39e32738529b4ae8f74acadb90..b2b14eada44231a619622a2baef27abdb798aa47 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +@@ -168,7 +168,23 @@ public class DummyGeneratorAccess implements GeneratorAccess { + public Fluid getFluid(BlockPosition blockposition) { + throw new UnsupportedOperationException("Not supported yet."); + } ++ // Paper start - if loaded util ++ @javax.annotation.Nullable ++ @Override ++ public IChunkAccess getChunkIfLoadedImmediately(int x, int z) { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ ++ @Override ++ public IBlockData getTypeIfLoaded(BlockPosition blockposition) { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } + ++ @Override ++ public Fluid getFluidIfLoaded(BlockPosition blockposition) { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ // Paper end + @Override + public WorldBorder getWorldBorder() { + throw new UnsupportedOperationException("Not supported yet."); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java +index 1aec70a1f1a9d8fd2cd06bde4033e19e769ab331..f72c13bedaa6fa45e26f5dcad564835bdd4af61f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java +@@ -17,7 +17,7 @@ import java.util.RandomAccess; + public class UnsafeList extends AbstractList implements List, RandomAccess, Cloneable, Serializable { + private static final long serialVersionUID = 8683452581112892191L; + +- private transient Object[] data; ++ private transient Object[] data; public final Object[] getRawDataArray() { return this.data; } // Paper - expose for raw get + private int size; + private int initialCapacity; + +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index 724c6a47846f4266c858b783f68f162e0508d2fa..c04d912adf0da8f7a5b75dd2f58739a11ca31601 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -118,7 +118,11 @@ public class SpigotConfig + } + } + } +- ++ // Paper start ++ SpigotConfig.save(); ++ } ++ public static void save() { ++ // Paper end + try + { + config.save( CONFIG_FILE ); diff --git a/patches/server-unmapped/0001/0006-Paper-Metrics.patch b/patches/server-unmapped/0001/0006-Paper-Metrics.patch new file mode 100644 index 0000000000..45ad445357 --- /dev/null +++ b/patches/server-unmapped/0001/0006-Paper-Metrics.patch @@ -0,0 +1,735 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Fri, 24 Mar 2017 23:56:01 -0500 +Subject: [PATCH] Paper Metrics + +Removes Spigot's mcstats metrics in favor of a system using bStats + +To disable for privacy or other reasons go to the bStats folder in your plugins folder +and edit the config.yml file present there. + +Please keep in mind the data collected is anonymous and collection should have no +tangible effect on server performance. The data is used to allow the authors of +PaperMC to track version and platform usage so that we can make better management +decisions on behalf of the project. + +diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b9e689d57705965721b5c55bc45d36657f360e4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/Metrics.java +@@ -0,0 +1,670 @@ ++package com.destroystokyo.paper; ++ ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Bukkit; ++import org.bukkit.configuration.file.YamlConfiguration; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.plugin.Plugin; ++ ++import org.json.simple.JSONArray; ++import org.json.simple.JSONObject; ++ ++import javax.net.ssl.HttpsURLConnection; ++import java.io.ByteArrayOutputStream; ++import java.io.DataOutputStream; ++import java.io.File; ++import java.io.IOException; ++import java.net.URL; ++import java.util.*; ++import java.util.concurrent.Callable; ++import java.util.concurrent.Executors; ++import java.util.concurrent.ScheduledExecutorService; ++import java.util.concurrent.TimeUnit; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++import java.util.zip.GZIPOutputStream; ++ ++/** ++ * bStats collects some data for plugin authors. ++ * ++ * Check out https://bStats.org/ to learn more about bStats! ++ */ ++public class Metrics { ++ ++ // Executor service for requests ++ // We use an executor service because the Bukkit scheduler is affected by server lags ++ private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); ++ ++ // The version of this bStats class ++ public static final int B_STATS_VERSION = 1; ++ ++ // The url to which the data is sent ++ private static final String URL = "https://bStats.org/submitData/server-implementation"; ++ ++ // Should failed requests be logged? ++ private static boolean logFailedRequests = false; ++ ++ // The logger for the failed requests ++ private static Logger logger = Logger.getLogger("bStats"); ++ ++ // The name of the server software ++ private final String name; ++ ++ // The uuid of the server ++ private final String serverUUID; ++ ++ // A list with all custom charts ++ private final List charts = new ArrayList<>(); ++ ++ /** ++ * Class constructor. ++ * ++ * @param name The name of the server software. ++ * @param serverUUID The uuid of the server. ++ * @param logFailedRequests Whether failed requests should be logged or not. ++ * @param logger The logger for the failed requests. ++ */ ++ public Metrics(String name, String serverUUID, boolean logFailedRequests, Logger logger) { ++ this.name = name; ++ this.serverUUID = serverUUID; ++ Metrics.logFailedRequests = logFailedRequests; ++ Metrics.logger = logger; ++ ++ // Start submitting the data ++ startSubmitting(); ++ } ++ ++ /** ++ * Adds a custom chart. ++ * ++ * @param chart The chart to add. ++ */ ++ public void addCustomChart(CustomChart chart) { ++ if (chart == null) { ++ throw new IllegalArgumentException("Chart cannot be null!"); ++ } ++ charts.add(chart); ++ } ++ ++ /** ++ * Starts the Scheduler which submits our data every 30 minutes. ++ */ ++ private void startSubmitting() { ++ final Runnable submitTask = this::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. ++ // WARNING: You must not modify any part of this Metrics class, including the submit delay or frequency! ++ // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! ++ long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); ++ long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); ++ scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); ++ scheduler.scheduleAtFixedRate(submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); ++ } ++ ++ /** ++ * Gets the plugin specific data. ++ * ++ * @return The plugin specific data. ++ */ ++ private JSONObject getPluginData() { ++ JSONObject data = new JSONObject(); ++ ++ data.put("pluginName", name); // Append the name of the server software ++ JSONArray customCharts = new JSONArray(); ++ for (CustomChart customChart : charts) { ++ // Add the data of the custom charts ++ JSONObject chart = customChart.getRequestJsonObject(); ++ if (chart == null) { // If the chart is null, we skip it ++ continue; ++ } ++ customCharts.add(chart); ++ } ++ data.put("customCharts", customCharts); ++ ++ return data; ++ } ++ ++ /** ++ * Gets the server specific data. ++ * ++ * @return The server specific data. ++ */ ++ private JSONObject getServerData() { ++ // OS specific data ++ String osName = System.getProperty("os.name"); ++ String osArch = System.getProperty("os.arch"); ++ String osVersion = System.getProperty("os.version"); ++ int coreCount = Runtime.getRuntime().availableProcessors(); ++ ++ JSONObject data = new JSONObject(); ++ ++ data.put("serverUUID", serverUUID); ++ ++ data.put("osName", osName); ++ data.put("osArch", osArch); ++ data.put("osVersion", osVersion); ++ data.put("coreCount", coreCount); ++ ++ return data; ++ } ++ ++ /** ++ * Collects the data and sends it afterwards. ++ */ ++ private void submitData() { ++ final JSONObject data = getServerData(); ++ ++ JSONArray pluginData = new JSONArray(); ++ pluginData.add(getPluginData()); ++ data.put("plugins", pluginData); ++ ++ try { ++ // We are still in the Thread of the timer, so nothing get blocked :) ++ sendData(data); ++ } catch (Exception e) { ++ // Something went wrong! :( ++ if (logFailedRequests) { ++ logger.log(Level.WARNING, "Could not submit stats of " + name, e); ++ } ++ } ++ } ++ ++ /** ++ * Sends the data to the bStats server. ++ * ++ * @param data The data to send. ++ * @throws Exception If the request failed. ++ */ ++ private static void sendData(JSONObject data) throws Exception { ++ if (data == null) { ++ throw new IllegalArgumentException("Data cannot be null!"); ++ } ++ HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); ++ ++ // Compress the data to save bandwidth ++ byte[] compressedData = compress(data.toString()); ++ ++ // Add headers ++ connection.setRequestMethod("POST"); ++ connection.addRequestProperty("Accept", "application/json"); ++ connection.addRequestProperty("Connection", "close"); ++ connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request ++ connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); ++ connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format ++ connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); ++ ++ // Send data ++ connection.setDoOutput(true); ++ DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); ++ outputStream.write(compressedData); ++ outputStream.flush(); ++ outputStream.close(); ++ ++ connection.getInputStream().close(); // We don't care about the response - Just send our data :) ++ } ++ ++ /** ++ * Gzips the given String. ++ * ++ * @param str The string to gzip. ++ * @return The gzipped String. ++ * @throws IOException If the compression failed. ++ */ ++ private static byte[] compress(final String str) throws IOException { ++ if (str == null) { ++ return null; ++ } ++ ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ++ GZIPOutputStream gzip = new GZIPOutputStream(outputStream); ++ gzip.write(str.getBytes("UTF-8")); ++ gzip.close(); ++ return outputStream.toByteArray(); ++ } ++ ++ /** ++ * Represents a custom chart. ++ */ ++ public static abstract class CustomChart { ++ ++ // The id of the chart ++ final String chartId; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ */ ++ CustomChart(String chartId) { ++ if (chartId == null || chartId.isEmpty()) { ++ throw new IllegalArgumentException("ChartId cannot be null or empty!"); ++ } ++ this.chartId = chartId; ++ } ++ ++ private JSONObject getRequestJsonObject() { ++ JSONObject chart = new JSONObject(); ++ chart.put("chartId", chartId); ++ try { ++ JSONObject data = getChartData(); ++ if (data == null) { ++ // If the data is null we don't send the chart. ++ return null; ++ } ++ chart.put("data", data); ++ } catch (Throwable t) { ++ if (logFailedRequests) { ++ logger.log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); ++ } ++ return null; ++ } ++ return chart; ++ } ++ ++ protected abstract JSONObject getChartData() throws Exception; ++ ++ } ++ ++ /** ++ * Represents a custom simple pie. ++ */ ++ public static class SimplePie extends CustomChart { ++ ++ private final Callable callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public SimplePie(String chartId, Callable callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ String value = callable.call(); ++ if (value == null || value.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("value", value); ++ return data; ++ } ++ } ++ ++ /** ++ * Represents a custom advanced pie. ++ */ ++ public static class AdvancedPie extends CustomChart { ++ ++ private final Callable> callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public AdvancedPie(String chartId, Callable> callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ JSONObject values = new JSONObject(); ++ Map map = callable.call(); ++ if (map == null || map.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ boolean allSkipped = true; ++ for (Map.Entry entry : map.entrySet()) { ++ if (entry.getValue() == 0) { ++ continue; // Skip this invalid ++ } ++ allSkipped = false; ++ values.put(entry.getKey(), entry.getValue()); ++ } ++ if (allSkipped) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("values", values); ++ return data; ++ } ++ } ++ ++ /** ++ * Represents a custom drilldown pie. ++ */ ++ public static class DrilldownPie extends CustomChart { ++ ++ private final Callable>> callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public DrilldownPie(String chartId, Callable>> callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ public JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ JSONObject values = new JSONObject(); ++ Map> map = callable.call(); ++ if (map == null || map.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ boolean reallyAllSkipped = true; ++ for (Map.Entry> entryValues : map.entrySet()) { ++ JSONObject value = new JSONObject(); ++ boolean allSkipped = true; ++ for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { ++ value.put(valueEntry.getKey(), valueEntry.getValue()); ++ allSkipped = false; ++ } ++ if (!allSkipped) { ++ reallyAllSkipped = false; ++ values.put(entryValues.getKey(), value); ++ } ++ } ++ if (reallyAllSkipped) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("values", values); ++ return data; ++ } ++ } ++ ++ /** ++ * Represents a custom single line chart. ++ */ ++ public static class SingleLineChart extends CustomChart { ++ ++ private final Callable callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public SingleLineChart(String chartId, Callable callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ int value = callable.call(); ++ if (value == 0) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("value", value); ++ return data; ++ } ++ ++ } ++ ++ /** ++ * Represents a custom multi line chart. ++ */ ++ public static class MultiLineChart extends CustomChart { ++ ++ private final Callable> callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public MultiLineChart(String chartId, Callable> callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ JSONObject values = new JSONObject(); ++ Map map = callable.call(); ++ if (map == null || map.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ boolean allSkipped = true; ++ for (Map.Entry entry : map.entrySet()) { ++ if (entry.getValue() == 0) { ++ continue; // Skip this invalid ++ } ++ allSkipped = false; ++ values.put(entry.getKey(), entry.getValue()); ++ } ++ if (allSkipped) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("values", values); ++ return data; ++ } ++ ++ } ++ ++ /** ++ * Represents a custom simple bar chart. ++ */ ++ public static class SimpleBarChart extends CustomChart { ++ ++ private final Callable> callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public SimpleBarChart(String chartId, Callable> callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ JSONObject values = new JSONObject(); ++ Map map = callable.call(); ++ if (map == null || map.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ for (Map.Entry entry : map.entrySet()) { ++ JSONArray categoryValues = new JSONArray(); ++ categoryValues.add(entry.getValue()); ++ values.put(entry.getKey(), categoryValues); ++ } ++ data.put("values", values); ++ return data; ++ } ++ ++ } ++ ++ /** ++ * Represents a custom advanced bar chart. ++ */ ++ public static class AdvancedBarChart extends CustomChart { ++ ++ private final Callable> callable; ++ ++ /** ++ * Class constructor. ++ * ++ * @param chartId The id of the chart. ++ * @param callable The callable which is used to request the chart data. ++ */ ++ public AdvancedBarChart(String chartId, Callable> callable) { ++ super(chartId); ++ this.callable = callable; ++ } ++ ++ @Override ++ protected JSONObject getChartData() throws Exception { ++ JSONObject data = new JSONObject(); ++ JSONObject values = new JSONObject(); ++ Map map = callable.call(); ++ if (map == null || map.isEmpty()) { ++ // Null = skip the chart ++ return null; ++ } ++ boolean allSkipped = true; ++ for (Map.Entry entry : map.entrySet()) { ++ if (entry.getValue().length == 0) { ++ continue; // Skip this invalid ++ } ++ allSkipped = false; ++ JSONArray categoryValues = new JSONArray(); ++ for (int categoryValue : entry.getValue()) { ++ categoryValues.add(categoryValue); ++ } ++ values.put(entry.getKey(), categoryValues); ++ } ++ if (allSkipped) { ++ // Null = skip the chart ++ return null; ++ } ++ data.put("values", values); ++ return data; ++ } ++ ++ } ++ ++ static class PaperMetrics { ++ static void startMetrics() { ++ // Get the config file ++ File configFile = new File(new File((File) MinecraftServer.getServer().options.valueOf("plugins"), "bStats"), "config.yml"); ++ YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); ++ ++ // Check if the config file exists ++ if (!config.isSet("serverUuid")) { ++ ++ // Add default values ++ config.addDefault("enabled", true); ++ // Every server gets it's unique random id. ++ config.addDefault("serverUuid", UUID.randomUUID().toString()); ++ // Should failed request be logged? ++ config.addDefault("logFailedRequests", false); ++ ++ // Inform the server owners about bStats ++ config.options().header( ++ "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + ++ "To honor their work, you should not disable it.\n" + ++ "This has nearly no effect on the server performance!\n" + ++ "Check out https://bStats.org/ to learn more :)" ++ ).copyDefaults(true); ++ try { ++ config.save(configFile); ++ } catch (IOException ignored) { ++ } ++ } ++ // Load the data ++ String serverUUID = config.getString("serverUuid"); ++ boolean logFailedRequests = config.getBoolean("logFailedRequests", false); ++ // Only start Metrics, if it's enabled in the config ++ if (config.getBoolean("enabled", true)) { ++ Metrics metrics = new Metrics("Paper", serverUUID, logFailedRequests, Bukkit.getLogger()); ++ ++ metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { ++ String minecraftVersion = Bukkit.getVersion(); ++ minecraftVersion = minecraftVersion.substring(minecraftVersion.indexOf("MC: ") + 4, minecraftVersion.length() - 1); ++ return minecraftVersion; ++ })); ++ ++ metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); ++ metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() || PaperConfig.isProxyOnlineMode() ? "online" : "offline")); ++ metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); ++ ++ metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { ++ Map> map = new HashMap<>(); ++ String javaVersion = System.getProperty("java.version"); ++ Map entry = new HashMap<>(); ++ entry.put(javaVersion, 1); ++ ++ // http://openjdk.java.net/jeps/223 ++ // Java decided to change their versioning scheme and in doing so modified the java.version system ++ // property to return $major[.$minor][.$secuity][-ea], as opposed to 1.$major.0_$identifier ++ // we can handle pre-9 by checking if the "major" is equal to "1", otherwise, 9+ ++ String majorVersion = javaVersion.split("\\.")[0]; ++ String release; ++ ++ int indexOf = javaVersion.lastIndexOf('.'); ++ ++ if (majorVersion.equals("1")) { ++ release = "Java " + javaVersion.substring(0, indexOf); ++ } else { ++ // of course, it really wouldn't be all that simple if they didn't add a quirk, now would it ++ // valid strings for the major may potentially include values such as -ea to deannotate a pre release ++ Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion); ++ if (versionMatcher.find()) { ++ majorVersion = versionMatcher.group(0); ++ } ++ release = "Java " + majorVersion; ++ } ++ map.put(release, entry); ++ ++ return map; ++ })); ++ ++ metrics.addCustomChart(new Metrics.DrilldownPie("legacy_plugins", () -> { ++ Map> map = new HashMap<>(); ++ ++ // count legacy plugins ++ int legacy = 0; ++ for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { ++ if (CraftMagicNumbers.isLegacy(plugin.getDescription())) { ++ legacy++; ++ } ++ } ++ ++ // insert real value as lower dimension ++ Map entry = new HashMap<>(); ++ entry.put(String.valueOf(legacy), 1); ++ ++ // create buckets as higher dimension ++ if (legacy == 0) { ++ map.put("0 \uD83D\uDE0E", entry); // :sunglasses: ++ } else if (legacy <= 5) { ++ map.put("1-5", entry); ++ } else if (legacy <= 10) { ++ map.put("6-10", entry); ++ } else if (legacy <= 25) { ++ map.put("11-25", entry); ++ } else if (legacy <= 50) { ++ map.put("26-50", entry); ++ } else { ++ map.put("50+ \uD83D\uDE2D", entry); // :cry: ++ } ++ ++ return map; ++ })); ++ } ++ ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 2c0514892d3993bef57ecf677cf8bb0fbe0216e4..da922f395f0fff0881ead893c900c5b2623f48f0 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -42,6 +42,7 @@ public class PaperConfig { + private static boolean verbose; + private static boolean fatalError; + /*========================================================================*/ ++ private static boolean metricsStarted; + + public static void init(File configFile) { + CONFIG_FILE = configFile; +@@ -84,6 +85,11 @@ public class PaperConfig { + for (Map.Entry entry : commands.entrySet()) { + MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Paper", entry.getValue()); + } ++ ++ if (!metricsStarted) { ++ Metrics.PaperMetrics.startMetrics(); ++ metricsStarted = true; ++ } + } + + static void readConfig(Class clazz, Object instance) { +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index c04d912adf0da8f7a5b75dd2f58739a11ca31601..3c93a497a790b8d800852db2ac48feca41f45cef 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -83,6 +83,7 @@ public class SpigotConfig + MinecraftServer.getServer().server.getCommandMap().register( entry.getKey(), "Spigot", entry.getValue() ); + } + ++ /* // Paper - Replace with our own + if ( metrics == null ) + { + try +@@ -94,6 +95,7 @@ public class SpigotConfig + Bukkit.getServer().getLogger().log( Level.SEVERE, "Could not start metrics service", ex ); + } + } ++ */ // Paper end + } + + static void readConfig(Class clazz, Object instance) diff --git a/patches/server-unmapped/0001/0007-Add-MinecraftKey-Information-to-Objects.patch b/patches/server-unmapped/0001/0007-Add-MinecraftKey-Information-to-Objects.patch new file mode 100644 index 0000000000..a390f7960d --- /dev/null +++ b/patches/server-unmapped/0001/0007-Add-MinecraftKey-Information-to-Objects.patch @@ -0,0 +1,144 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 Jul 2018 01:40:13 -0400 +Subject: [PATCH] Add MinecraftKey Information to Objects + +Stores the reference to the objects respective MinecraftKey + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index 68cd4134cb6a00c1768100462f8e9e94f3fa6279..6943524c2dd8b12691b8ac5b08daee823ce50c3d 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -208,7 +208,7 @@ public class PaperCommand extends Command { + + Collection entities = world.entitiesById.values(); + entities.forEach(e -> { +- MinecraftKey key = new MinecraftKey(""); // TODO: update in next patch ++ MinecraftKey key = e.getMinecraftKey(); + + MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); + ChunkCoordIntPair chunk = new ChunkCoordIntPair(e.chunkX, e.chunkZ); +diff --git a/src/main/java/net/minecraft/server/KeyedObject.java b/src/main/java/net/minecraft/server/KeyedObject.java +new file mode 100644 +index 0000000000000000000000000000000000000000..500477f0d98a277145c2b0dbb4d1496e2a588542 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/KeyedObject.java +@@ -0,0 +1,11 @@ ++package net.minecraft.server; ++ ++import net.minecraft.resources.MinecraftKey; ++ ++public interface KeyedObject { ++ MinecraftKey getMinecraftKey(); ++ default String getMinecraftKeyString() { ++ MinecraftKey key = getMinecraftKey(); ++ return key != null ? key.toString() : null; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 3c8767d5fab575e61ca179b517f0bff1a38405ce..ec553e7d7595ef3652bfa3325a07483bb3c32245 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -136,7 +136,7 @@ import org.bukkit.event.player.PlayerTeleportEvent; + import org.bukkit.plugin.PluginManager; + // CraftBukkit end + +-public abstract class Entity implements INamableTileEntity, ICommandListener { ++public abstract class Entity implements INamableTileEntity, ICommandListener, net.minecraft.server.KeyedObject { // Paper + + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; +@@ -1762,12 +1762,31 @@ public abstract class Entity implements INamableTileEntity, ICommandListener { + return true; + } + ++ // Paper start ++ private MinecraftKey entityKey; ++ private String entityKeyString; ++ ++ @Override ++ public MinecraftKey getMinecraftKey() { ++ if (entityKey == null) { ++ this.entityKey = EntityTypes.getName(this.getEntityType()); ++ this.entityKeyString = this.entityKey != null ? this.entityKey.toString() : null; ++ } ++ return entityKey; ++ } ++ ++ @Override ++ public String getMinecraftKeyString() { ++ getMinecraftKey(); // Try to load if it doesn't exists. see: https://github.com/PaperMC/Paper/issues/1280 ++ return entityKeyString; ++ } + @Nullable + public final String getSaveID() { + EntityTypes entitytypes = this.getEntityType(); + MinecraftKey minecraftkey = EntityTypes.getName(entitytypes); + +- return entitytypes.a() && minecraftkey != null ? minecraftkey.toString() : null; ++ return entitytypes != null && entitytypes.isPersistable() ? getMinecraftKeyString() : null; ++ // Paper end + } + + protected abstract void loadData(NBTTagCompound nbttagcompound); +diff --git a/src/main/java/net/minecraft/world/entity/EntityTypes.java b/src/main/java/net/minecraft/world/entity/EntityTypes.java +index ac57ab9992e141c91cf48f033148ad78433b364c..dc92b112770955f9fa49a408262da2e5bbc4bf98 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityTypes.java ++++ b/src/main/java/net/minecraft/world/entity/EntityTypes.java +@@ -384,6 +384,7 @@ public class EntityTypes { + } + } + ++ public boolean isPersistable() { return a(); } // Paper - OBFHELPER + public boolean a() { + return this.bi; + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +index 72d43b38de3e797a509de6591874af12fa7b9ec0..2b58ae6d91fe0d0f36eedbb78a3c8a8a66d92405 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +@@ -23,7 +23,7 @@ import org.bukkit.inventory.InventoryHolder; + + import org.spigotmc.CustomTimingsHandler; // Spigot + +-public abstract class TileEntity { ++public abstract class TileEntity implements net.minecraft.server.KeyedObject { // Paper + + public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot + // CraftBukkit start - data containers +@@ -31,7 +31,7 @@ public abstract class TileEntity { + public CraftPersistentDataContainer persistentDataContainer; + // CraftBukkit end + private static final Logger LOGGER = LogManager.getLogger(); +- private final TileEntityTypes tileType; ++ private final TileEntityTypes tileType; public TileEntityTypes getTileEntityType() { return tileType; } // Paper - OBFHELPER + @Nullable + protected World world; + protected BlockPosition position; +@@ -45,6 +45,26 @@ public abstract class TileEntity { + this.tileType = tileentitytypes; + } + ++ // Paper start ++ private String tileEntityKeyString = null; ++ private MinecraftKey tileEntityKey = null; ++ ++ @Override ++ public MinecraftKey getMinecraftKey() { ++ if (tileEntityKey == null) { ++ tileEntityKey = TileEntityTypes.a(this.getTileEntityType()); ++ tileEntityKeyString = tileEntityKey != null ? tileEntityKey.toString() : null; ++ } ++ return tileEntityKey; ++ } ++ ++ @Override ++ public String getMinecraftKeyString() { ++ getMinecraftKey(); // Try to load if it doesn't exists. ++ return tileEntityKeyString; ++ } ++ // Paper end ++ + @Nullable + public World getWorld() { + return this.world; diff --git a/patches/server-unmapped/0001/0008-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch b/patches/server-unmapped/0001/0008-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch new file mode 100644 index 0000000000..873dc9de7e --- /dev/null +++ b/patches/server-unmapped/0001/0008-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch @@ -0,0 +1,171 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 Jul 2018 02:10:36 -0400 +Subject: [PATCH] Store reference to current Chunk for Entity and Block + Entities + +This enables us a fast reference to the entities current chunk instead +of having to look it up by hashmap lookups. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index ec553e7d7595ef3652bfa3325a07483bb3c32245..2bea2f4748cadf479dd4f89792ef5ffdd88e9cab 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -261,7 +261,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public boolean isChunkLoaded() { +- return world.isChunkLoaded((int) Math.floor(this.locX()) >> 4, (int) Math.floor(this.locZ()) >> 4); ++ return getCurrentChunk() != null; + } + // CraftBukkit end + +@@ -1763,6 +1763,23 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + // Paper start ++ public java.lang.ref.WeakReference currentChunk = null; ++ ++ public void setCurrentChunk(net.minecraft.world.level.chunk.Chunk chunk) { ++ this.currentChunk = chunk != null ? new java.lang.ref.WeakReference<>(chunk) : null; ++ } ++ /** ++ * Returns the entities current registered chunk. If the entity is not added to a chunk yet, it will return null ++ */ ++ public net.minecraft.world.level.chunk.Chunk getCurrentChunk() { ++ final net.minecraft.world.level.chunk.Chunk chunk = currentChunk != null ? currentChunk.get() : null; ++ if (chunk != null && chunk.loaded) { ++ return chunk; ++ } ++ ++ return !inChunk ? null : ((WorldServer)world).getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(chunkX, chunkZ); ++ } ++ + private MinecraftKey entityKey; + private String entityKeyString; + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +index 2b58ae6d91fe0d0f36eedbb78a3c8a8a66d92405..75110c41af3e0097aef65091a2497dd87d08b4b2 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +@@ -11,6 +11,7 @@ import net.minecraft.world.level.World; + import net.minecraft.world.level.block.EnumBlockMirror; + import net.minecraft.world.level.block.EnumBlockRotation; + import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.Chunk; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + import org.apache.logging.log4j.util.Supplier; +@@ -63,6 +64,15 @@ public abstract class TileEntity implements net.minecraft.server.KeyedObject { / + getMinecraftKey(); // Try to load if it doesn't exists. + return tileEntityKeyString; + } ++ ++ private java.lang.ref.WeakReference currentChunk = null; ++ public Chunk getCurrentChunk() { ++ final Chunk chunk = currentChunk != null ? currentChunk.get() : null; ++ return chunk != null && chunk.loaded ? chunk : null; ++ } ++ public void setCurrentChunk(Chunk chunk) { ++ this.currentChunk = chunk != null ? new java.lang.ref.WeakReference<>(chunk) : null; ++ } + // Paper end + + @Nullable +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index b15200c2a3923bd8be2ee5e73fdadfeea3e3a8dc..929f6fcd4b9f1b9a1488e170d6a77a5d64beecf3 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -90,11 +90,36 @@ public class Chunk implements IChunkAccess { + this(world, chunkcoordintpair, biomestorage, ChunkConverter.a, TickListEmpty.b(), TickListEmpty.b(), 0L, (ChunkSection[]) null, (Consumer) null); + } + ++ // Paper start ++ private class TileEntityHashMap extends java.util.HashMap { ++ @Override ++ public TileEntity put(BlockPosition key, TileEntity value) { ++ TileEntity replaced = super.put(key, value); ++ if (replaced != null) { ++ replaced.setCurrentChunk(null); ++ } ++ if (value != null) { ++ value.setCurrentChunk(Chunk.this); ++ } ++ return replaced; ++ } ++ ++ @Override ++ public TileEntity remove(Object key) { ++ TileEntity removed = super.remove(key); ++ if (removed != null) { ++ removed.setCurrentChunk(null); ++ } ++ return removed; ++ } ++ } ++ // Paper end ++ + public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage, ChunkConverter chunkconverter, TickList ticklist, TickList ticklist1, long i, @Nullable ChunkSection[] achunksection, @Nullable Consumer consumer) { + this.sections = new ChunkSection[16]; + this.e = Maps.newHashMap(); + this.heightMap = Maps.newEnumMap(HeightMap.Type.class); +- this.tileEntities = Maps.newHashMap(); ++ this.tileEntities = new TileEntityHashMap(); // Paper + this.l = Maps.newHashMap(); + this.m = Maps.newHashMap(); + this.n = new ShortList[16]; +@@ -505,6 +530,7 @@ public class Chunk implements IChunkAccess { + } + + entity.inChunk = true; ++ entity.setCurrentChunk(this); // Paper + entity.chunkX = this.loc.x; + entity.chunkY = k; + entity.chunkZ = this.loc.z; +@@ -517,6 +543,7 @@ public class Chunk implements IChunkAccess { + ((HeightMap) this.heightMap.get(heightmap_type)).a(along); + } + ++ public final void removeEntity(Entity entity) { this.b(entity); } // Paper - OBFHELPER + public void b(Entity entity) { + this.a(entity, entity.chunkY); + } +@@ -531,7 +558,12 @@ public class Chunk implements IChunkAccess { + i = this.entitySlices.length - 1; + } + +- this.entitySlices[i].remove(entity); ++ // Paper start ++ if (entity.currentChunk != null && entity.currentChunk.get() == this) entity.setCurrentChunk(null); ++ if (!this.entitySlices[i].remove(entity)) { ++ return; ++ } ++ // Paper end + this.entities.remove(entity); // Paper + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index df7e5f1d17ddfeffc15df02906c3bf9f9461d82b..eea242af23825ad29ada6e997205e87edffb6bb9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -145,6 +145,7 @@ import net.minecraft.world.entity.vehicle.EntityMinecartMobSpawner; + import net.minecraft.world.entity.vehicle.EntityMinecartRideable; + import net.minecraft.world.entity.vehicle.EntityMinecartTNT; + import net.minecraft.world.phys.AxisAlignedBB; ++import org.bukkit.Chunk; // Paper + import org.bukkit.EntityEffect; + import org.bukkit.Location; + import org.bukkit.Server; +@@ -186,6 +187,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + this.entity = entity; + } + ++ @Override ++ public Chunk getChunk() { ++ net.minecraft.world.level.chunk.Chunk currentChunk = entity.getCurrentChunk(); ++ return currentChunk != null ? currentChunk.bukkitChunk : getLocation().getChunk(); ++ } ++ + public static CraftEntity getEntity(CraftServer server, Entity entity) { + /* + * Order is *EXTREMELY* important -- keep it right! =D diff --git a/patches/server-unmapped/0001/0009-Store-counts-for-each-Entity-Block-Entity-Type.patch b/patches/server-unmapped/0001/0009-Store-counts-for-each-Entity-Block-Entity-Type.patch new file mode 100644 index 0000000000..73d224306f --- /dev/null +++ b/patches/server-unmapped/0001/0009-Store-counts-for-each-Entity-Block-Entity-Type.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 Jul 2018 02:13:59 -0400 +Subject: [PATCH] Store counts for each Entity/Block Entity Type + +Opens door for future patches to optimize performance + +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index 929f6fcd4b9f1b9a1488e170d6a77a5d64beecf3..acdcece38a4b30d6c89eb4342918ae8997db9f0b 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -91,15 +91,19 @@ public class Chunk implements IChunkAccess { + } + + // Paper start ++ public final co.aikar.util.Counter entityCounts = new co.aikar.util.Counter<>(); ++ public final co.aikar.util.Counter tileEntityCounts = new co.aikar.util.Counter<>(); + private class TileEntityHashMap extends java.util.HashMap { + @Override + public TileEntity put(BlockPosition key, TileEntity value) { + TileEntity replaced = super.put(key, value); + if (replaced != null) { + replaced.setCurrentChunk(null); ++ tileEntityCounts.decrement(replaced.getMinecraftKeyString()); + } + if (value != null) { + value.setCurrentChunk(Chunk.this); ++ tileEntityCounts.increment(value.getMinecraftKeyString()); + } + return replaced; + } +@@ -109,6 +113,7 @@ public class Chunk implements IChunkAccess { + TileEntity removed = super.remove(key); + if (removed != null) { + removed.setCurrentChunk(null); ++ tileEntityCounts.decrement(removed.getMinecraftKeyString()); + } + return removed; + } +@@ -529,6 +534,7 @@ public class Chunk implements IChunkAccess { + k = this.entitySlices.length - 1; + } + ++ if (!entity.inChunk || entity.getCurrentChunk() != this) entityCounts.increment(entity.getMinecraftKeyString()); // Paper + entity.inChunk = true; + entity.setCurrentChunk(this); // Paper + entity.chunkX = this.loc.x; +@@ -563,6 +569,7 @@ public class Chunk implements IChunkAccess { + if (!this.entitySlices[i].remove(entity)) { + return; + } ++ entityCounts.decrement(entity.getMinecraftKeyString()); + // Paper end + this.entities.remove(entity); // Paper + } diff --git a/patches/server-unmapped/0001/0010-Timings-v2.patch b/patches/server-unmapped/0001/0010-Timings-v2.patch new file mode 100644 index 0000000000..bda146a6a5 --- /dev/null +++ b/patches/server-unmapped/0001/0010-Timings-v2.patch @@ -0,0 +1,2304 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 04:00:11 -0600 +Subject: [PATCH] Timings v2 + + +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fed920e5ec65409377f181d74dcf9274d45aadc1 +--- /dev/null ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -0,0 +1,151 @@ ++package co.aikar.timings; ++ ++import com.google.common.collect.MapMaker; ++import net.minecraft.commands.CustomFunction; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.entity.TileEntity; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.scheduler.BukkitTask; ++ ++import org.bukkit.craftbukkit.scheduler.CraftTask; ++ ++import java.util.Map; ++ ++// TODO: Re-implement missing timers ++public final class MinecraftTimings { ++ ++ public static final Timing serverOversleep = Timings.ofSafe("Server Oversleep"); ++ public static final Timing playerListTimer = Timings.ofSafe("Player List"); ++ public static final Timing commandFunctionsTimer = Timings.ofSafe("Command Functions"); ++ public static final Timing connectionTimer = Timings.ofSafe("Connection Handler"); ++ public static final Timing tickablesTimer = Timings.ofSafe("Tickables"); ++ public static final Timing minecraftSchedulerTimer = Timings.ofSafe("Minecraft Scheduler"); ++ public static final Timing bukkitSchedulerTimer = Timings.ofSafe("Bukkit Scheduler"); ++ public static final Timing bukkitSchedulerPendingTimer = Timings.ofSafe("Bukkit Scheduler - Pending"); ++ public static final Timing bukkitSchedulerFinishTimer = Timings.ofSafe("Bukkit Scheduler - Finishing"); ++ public static final Timing chunkIOTickTimer = Timings.ofSafe("ChunkIOTick"); ++ public static final Timing timeUpdateTimer = Timings.ofSafe("Time Update"); ++ public static final Timing serverCommandTimer = Timings.ofSafe("Server Command"); ++ public static final Timing savePlayers = Timings.ofSafe("Save Players"); ++ ++ public static final Timing tickEntityTimer = Timings.ofSafe("## tickEntity"); ++ public static final Timing tickTileEntityTimer = Timings.ofSafe("## tickTileEntity"); ++ public static final Timing packetProcessTimer = Timings.ofSafe("## Packet Processing"); ++ public static final Timing scheduledBlocksTimer = Timings.ofSafe("## Scheduled Blocks"); ++ public static final Timing structureGenerationTimer = Timings.ofSafe("Structure Generation"); ++ ++ public static final Timing processQueueTimer = Timings.ofSafe("processQueue"); ++ public static final Timing processTasksTimer = Timings.ofSafe("processTasks"); ++ ++ public static final Timing playerCommandTimer = Timings.ofSafe("playerCommand"); ++ ++ public static final Timing entityActivationCheckTimer = Timings.ofSafe("entityActivationCheck"); ++ ++ public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); ++ public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); ++ ++ private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap(); ++ ++ private MinecraftTimings() {} ++ ++ public static Timing getInternalTaskName(String taskName) { ++ return Timings.ofSafe(taskName); ++ } ++ ++ /** ++ * Gets a timer associated with a plugins tasks. ++ * @param bukkitTask ++ * @param period ++ * @return ++ */ ++ public static Timing getPluginTaskTimings(BukkitTask bukkitTask, long period) { ++ if (!bukkitTask.isSync()) { ++ return NullTimingHandler.NULL; ++ } ++ Plugin plugin; ++ ++ CraftTask craftTask = (CraftTask) bukkitTask; ++ ++ final Class taskClass = craftTask.getTaskClass(); ++ if (bukkitTask.getOwner() != null) { ++ plugin = bukkitTask.getOwner(); ++ } else { ++ plugin = TimingsManager.getPluginByClassloader(taskClass); ++ } ++ ++ final String taskname = taskNameCache.computeIfAbsent(taskClass, clazz -> { ++ try { ++ String clsName = !clazz.isMemberClass() ++ ? clazz.getName() ++ : clazz.getCanonicalName(); ++ if (clsName != null && clsName.contains("$Lambda$")) { ++ clsName = clsName.replaceAll("(Lambda\\$.*?)/.*", "$1"); ++ } ++ return clsName != null ? clsName : "UnknownTask"; ++ } catch (Throwable ex) { ++ new Exception("Error occurred detecting class name", ex).printStackTrace(); ++ return "MangledClassFile"; ++ } ++ }); ++ ++ StringBuilder name = new StringBuilder(64); ++ name.append("Task: ").append(taskname); ++ if (period > 0) { ++ name.append(" (interval:").append(period).append(")"); ++ } else { ++ name.append(" (Single)"); ++ } ++ ++ if (plugin == null) { ++ return Timings.ofSafe(null, name.toString()); ++ } ++ ++ return Timings.ofSafe(plugin, name.toString()); ++ } ++ ++ /** ++ * Get a named timer for the specified entity type to track type specific timings. ++ * @param entityType ++ * @return ++ */ ++ public static Timing getEntityTimings(String entityType, String type) { ++ return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType + " - " + type, tickEntityTimer); ++ } ++ ++ /** ++ * Get a named timer for the specified tile entity type to track type specific timings. ++ * @param entity ++ * @return ++ */ ++ public static Timing getTileEntityTimings(TileEntity entity) { ++ String entityType = entity.getClass().getName(); ++ return Timings.ofSafe("Minecraft", "## tickTileEntity - " + entityType, tickTileEntityTimer); ++ } ++ public static Timing getCancelTasksTimer() { ++ return Timings.ofSafe("Cancel Tasks"); ++ } ++ public static Timing getCancelTasksTimer(Plugin plugin) { ++ return Timings.ofSafe(plugin, "Cancel Tasks"); ++ } ++ ++ public static void stopServer() { ++ TimingsManager.stopServer(); ++ } ++ ++ public static Timing getBlockTiming(Block block) { ++ return Timings.ofSafe("## Scheduled Block: " + block.toString(), scheduledBlocksTimer); ++ } ++/* ++ public static Timing getStructureTiming(StructureGenerator structureGenerator) { ++ return Timings.ofSafe("Structure Generator - " + structureGenerator.getName(), structureGenerationTimer); ++ }*/ ++ ++ public static Timing getPacketTiming(Packet packet) { ++ return Timings.ofSafe("## Packet - " + packet.getClass().getName(), packetProcessTimer); ++ } ++ ++ public static Timing getCommandFunctionTiming(CustomFunction function) { ++ return Timings.ofSafe("Command Function - " + function.getMinecraftKey().toString()); ++ } ++} +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d4ebcf8f66197299256bd6b65710a1488c90ea41 +--- /dev/null ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -0,0 +1,377 @@ ++/* ++ * This file is licensed under the MIT License (MIT). ++ * ++ * Copyright (c) 2014 Daniel Ennis ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a copy ++ * of this software and associated documentation files (the "Software"), to deal ++ * in the Software without restriction, including without limitation the rights ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++package co.aikar.timings; ++ ++import com.google.common.collect.Sets; ++import net.minecraft.server.MinecraftServer; ++import org.apache.commons.lang.StringUtils; ++import org.bukkit.Bukkit; ++import org.bukkit.ChatColor; ++import org.bukkit.Material; ++import org.bukkit.configuration.ConfigurationSection; ++import org.bukkit.configuration.MemorySection; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++import org.bukkit.entity.EntityType; ++import org.json.simple.JSONObject; ++import org.json.simple.JSONValue; ++ ++import java.io.ByteArrayOutputStream; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++import java.lang.management.ManagementFactory; ++import java.lang.management.OperatingSystemMXBean; ++import java.lang.management.RuntimeMXBean; ++import java.net.HttpURLConnection; ++import java.net.InetAddress; ++import java.net.URL; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.logging.Level; ++import java.util.zip.GZIPOutputStream; ++ ++import static co.aikar.timings.TimingsManager.HISTORY; ++import static co.aikar.util.JSONUtil.appendObjectData; ++import static co.aikar.util.JSONUtil.createObject; ++import static co.aikar.util.JSONUtil.pair; ++import static co.aikar.util.JSONUtil.toArray; ++import static co.aikar.util.JSONUtil.toArrayMapper; ++import static co.aikar.util.JSONUtil.toObjectMapper; ++ ++@SuppressWarnings({"rawtypes", "SuppressionAnnotation"}) ++public class TimingsExport extends Thread { ++ ++ private final TimingsReportListener listeners; ++ private final Map out; ++ private final TimingHistory[] history; ++ private static long lastReport = 0; ++ ++ private TimingsExport(TimingsReportListener listeners, Map out, TimingHistory[] history) { ++ super("Timings paste thread"); ++ this.listeners = listeners; ++ this.out = out; ++ this.history = history; ++ } ++ ++ /** ++ * Checks if any pending reports are being requested, and builds one if needed. ++ */ ++ public static void reportTimings() { ++ if (Timings.requestingReport.isEmpty()) { ++ return; ++ } ++ TimingsReportListener listeners = new TimingsReportListener(Timings.requestingReport); ++ listeners.addConsoleIfNeeded(); ++ ++ Timings.requestingReport.clear(); ++ long now = System.currentTimeMillis(); ++ final long lastReportDiff = now - lastReport; ++ if (lastReportDiff < 60000) { ++ listeners.sendMessage(ChatColor.RED + "Please wait at least 1 minute in between Timings reports. (" + (int)((60000 - lastReportDiff) / 1000) + " seconds)"); ++ listeners.done(); ++ return; ++ } ++ final long lastStartDiff = now - TimingsManager.timingStart; ++ if (lastStartDiff < 180000) { ++ listeners.sendMessage(ChatColor.RED + "Please wait at least 3 minutes before generating a Timings report. Unlike Timings v1, v2 benefits from longer timings and is not as useful with short timings. (" + (int)((180000 - lastStartDiff) / 1000) + " seconds)"); ++ listeners.done(); ++ return; ++ } ++ listeners.sendMessage(ChatColor.GREEN + "Preparing Timings Report..."); ++ lastReport = now; ++ Map parent = createObject( ++ // Get some basic system details about the server ++ pair("version", Bukkit.getVersion()), ++ pair("maxplayers", Bukkit.getMaxPlayers()), ++ pair("start", TimingsManager.timingStart / 1000), ++ pair("end", System.currentTimeMillis() / 1000), ++ pair("online-mode", Bukkit.getServer().getOnlineMode()), ++ pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000), ++ pair("datapacks", toArrayMapper(MinecraftServer.getServer().getResourcePackRepository().d(), pack -> { ++ // Don't feel like obf helper'ing these, non fatal if its temp missed. ++ return ChatColor.stripColor(CraftChatMessage.fromComponent(pack.a(true))); ++ })) ++ ); ++ if (!TimingsManager.privacy) { ++ appendObjectData(parent, ++ pair("server", Bukkit.getUnsafe().getTimingsServerName()), ++ pair("motd", Bukkit.getServer().getMotd()), ++ pair("icon", Bukkit.getServer().getServerIcon().getData()) ++ ); ++ } ++ ++ final Runtime runtime = Runtime.getRuntime(); ++ RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); ++ ++ OperatingSystemMXBean osInfo = ManagementFactory.getOperatingSystemMXBean(); ++ ++ parent.put("system", createObject( ++ pair("timingcost", getCost()), ++ pair("loadavg", osInfo.getSystemLoadAverage()), ++ pair("name", System.getProperty("os.name")), ++ pair("version", System.getProperty("os.version")), ++ pair("jvmversion", System.getProperty("java.version")), ++ pair("arch", System.getProperty("os.arch")), ++ pair("maxmem", runtime.maxMemory()), ++ pair("memory", createObject( ++ pair("heap", ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().toString()), ++ pair("nonheap", ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().toString()), ++ pair("finalizing", ManagementFactory.getMemoryMXBean().getObjectPendingFinalizationCount()) ++ )), ++ pair("cpu", runtime.availableProcessors()), ++ pair("runtime", runtimeBean.getUptime()), ++ pair("flags", StringUtils.join(runtimeBean.getInputArguments(), " ")), ++ pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), input -> pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime())))) ++ ) ++ ); ++ ++ parent.put("worlds", toObjectMapper(MinecraftServer.getServer().getWorlds(), world -> { ++ if (world.getWorldData().getName().equals("worldeditregentempworld")) return null; ++ return pair(world.getWorldData().getName(), createObject( ++ pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> { ++ return pair(rule, world.getWorld().getGameRuleValue(rule)); ++ })), ++ pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance()) ++ )); ++ })); ++ ++ Set tileEntityTypeSet = Sets.newHashSet(); ++ Set entityTypeSet = Sets.newHashSet(); ++ ++ int size = HISTORY.size(); ++ TimingHistory[] history = new TimingHistory[size + 1]; ++ int i = 0; ++ for (TimingHistory timingHistory : HISTORY) { ++ tileEntityTypeSet.addAll(timingHistory.tileEntityTypeSet); ++ entityTypeSet.addAll(timingHistory.entityTypeSet); ++ history[i++] = timingHistory; ++ } ++ ++ history[i] = new TimingHistory(); // Current snapshot ++ tileEntityTypeSet.addAll(history[i].tileEntityTypeSet); ++ entityTypeSet.addAll(history[i].entityTypeSet); ++ ++ ++ Map handlers = createObject(); ++ Map groupData; ++ synchronized (TimingIdentifier.GROUP_MAP) { ++ for (TimingIdentifier.TimingGroup group : TimingIdentifier.GROUP_MAP.values()) { ++ synchronized (group.handlers) { ++ for (TimingHandler id : group.handlers) { ++ ++ if (!id.isTimed() && !id.isSpecial()) { ++ continue; ++ } ++ ++ String name = id.identifier.name; ++ if (name.startsWith("##")) { ++ name = name.substring(3); ++ } ++ handlers.put(id.id, toArray( ++ group.id, ++ name ++ )); ++ } ++ } ++ } ++ ++ groupData = toObjectMapper( ++ TimingIdentifier.GROUP_MAP.values(), group -> pair(group.id, group.name)); ++ } ++ ++ parent.put("idmap", createObject( ++ pair("groups", groupData), ++ pair("handlers", handlers), ++ pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), input -> pair(input.getValue(), input.getKey()))), ++ pair("tileentity", ++ toObjectMapper(tileEntityTypeSet, input -> pair(input.ordinal(), input.name()))), ++ pair("entity", ++ toObjectMapper(entityTypeSet, input -> pair(input.ordinal(), input.name()))) ++ )); ++ ++ // Information about loaded plugins ++ ++ parent.put("plugins", toObjectMapper(Bukkit.getPluginManager().getPlugins(), ++ plugin -> pair(plugin.getName(), createObject( ++ pair("version", plugin.getDescription().getVersion()), ++ pair("description", String.valueOf(plugin.getDescription().getDescription()).trim()), ++ pair("website", plugin.getDescription().getWebsite()), ++ pair("authors", StringUtils.join(plugin.getDescription().getAuthors(), ", ")) ++ )))); ++ ++ ++ ++ // Information on the users Config ++ ++ parent.put("config", createObject( ++ pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)), ++ pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), ++ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)) ++ )); ++ ++ new TimingsExport(listeners, parent, history).start(); ++ } ++ ++ static long getCost() { ++ // Benchmark the users System.nanotime() for cost basis ++ int passes = 100; ++ TimingHandler SAMPLER1 = Timings.ofSafe("Timings Sampler 1"); ++ TimingHandler SAMPLER2 = Timings.ofSafe("Timings Sampler 2"); ++ TimingHandler SAMPLER3 = Timings.ofSafe("Timings Sampler 3"); ++ TimingHandler SAMPLER4 = Timings.ofSafe("Timings Sampler 4"); ++ TimingHandler SAMPLER5 = Timings.ofSafe("Timings Sampler 5"); ++ TimingHandler SAMPLER6 = Timings.ofSafe("Timings Sampler 6"); ++ ++ long start = System.nanoTime(); ++ for (int i = 0; i < passes; i++) { ++ SAMPLER1.startTiming(); ++ SAMPLER2.startTiming(); ++ SAMPLER3.startTiming(); ++ SAMPLER3.stopTiming(); ++ SAMPLER4.startTiming(); ++ SAMPLER5.startTiming(); ++ SAMPLER6.startTiming(); ++ SAMPLER6.stopTiming(); ++ SAMPLER5.stopTiming(); ++ SAMPLER4.stopTiming(); ++ SAMPLER2.stopTiming(); ++ SAMPLER1.stopTiming(); ++ } ++ long timingsCost = (System.nanoTime() - start) / passes / 6; ++ SAMPLER1.reset(true); ++ SAMPLER2.reset(true); ++ SAMPLER3.reset(true); ++ SAMPLER4.reset(true); ++ SAMPLER5.reset(true); ++ SAMPLER6.reset(true); ++ return timingsCost; ++ } ++ ++ private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) { ++ ++ JSONObject object = new JSONObject(); ++ for (String key : config.getKeys(false)) { ++ String fullKey = (parentKey != null ? parentKey + "." + key : key); ++ if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld")) { ++ continue; ++ } ++ final Object val = config.get(key); ++ ++ object.put(key, valAsJSON(val, fullKey)); ++ } ++ return object; ++ } ++ ++ private static Object valAsJSON(Object val, final String parentKey) { ++ if (!(val instanceof MemorySection)) { ++ if (val instanceof List) { ++ Iterable v = (Iterable) val; ++ return toArrayMapper(v, input -> valAsJSON(input, parentKey)); ++ } else { ++ return String.valueOf(val); ++ } ++ } else { ++ return mapAsJSON((ConfigurationSection) val, parentKey); ++ } ++ } ++ ++ @Override ++ public void run() { ++ out.put("data", toArrayMapper(history, TimingHistory::export)); ++ ++ ++ String response = null; ++ String timingsURL = null; ++ try { ++ HttpURLConnection con = (HttpURLConnection) new URL("http://timings.aikar.co/post").openConnection(); ++ con.setDoOutput(true); ++ String hostName = "BrokenHost"; ++ try { ++ hostName = InetAddress.getLocalHost().getHostName(); ++ } catch (Exception ignored) {} ++ con.setRequestProperty("User-Agent", "Paper/" + Bukkit.getUnsafe().getTimingsServerName() + "/" + hostName); ++ con.setRequestMethod("POST"); ++ con.setInstanceFollowRedirects(false); ++ ++ OutputStream request = new GZIPOutputStream(con.getOutputStream()) {{ ++ this.def.setLevel(7); ++ }}; ++ ++ request.write(JSONValue.toJSONString(out).getBytes("UTF-8")); ++ request.close(); ++ ++ response = getResponse(con); ++ ++ if (con.getResponseCode() != 302) { ++ listeners.sendMessage( ++ ChatColor.RED + "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage()); ++ listeners.sendMessage(ChatColor.RED + "Check your logs for more information"); ++ if (response != null) { ++ Bukkit.getLogger().log(Level.SEVERE, response); ++ } ++ return; ++ } ++ ++ timingsURL = con.getHeaderField("Location"); ++ listeners.sendMessage(ChatColor.GREEN + "View Timings Report: " + timingsURL); ++ ++ if (response != null && !response.isEmpty()) { ++ Bukkit.getLogger().log(Level.INFO, "Timing Response: " + response); ++ } ++ } catch (IOException ex) { ++ listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information"); ++ if (response != null) { ++ Bukkit.getLogger().log(Level.SEVERE, response); ++ } ++ Bukkit.getLogger().log(Level.SEVERE, "Could not paste timings", ex); ++ } finally { ++ this.listeners.done(timingsURL); ++ } ++ } ++ ++ private String getResponse(HttpURLConnection con) throws IOException { ++ InputStream is = null; ++ try { ++ is = con.getInputStream(); ++ ByteArrayOutputStream bos = new ByteArrayOutputStream(); ++ ++ byte[] b = new byte[1024]; ++ int bytesRead; ++ while ((bytesRead = is.read(b)) != -1) { ++ bos.write(b, 0, bytesRead); ++ } ++ return bos.toString(); ++ ++ } catch (IOException ex) { ++ listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information"); ++ Bukkit.getLogger().log(Level.WARNING, con.getResponseMessage(), ex); ++ return null; ++ } finally { ++ if (is != null) { ++ is.close(); ++ } ++ } ++ } ++} +diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fa154ed68187a2020e814db6345a8cc1119ab4ba +--- /dev/null ++++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +@@ -0,0 +1,119 @@ ++package co.aikar.timings; ++ ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.storage.WorldDataServer; ++ ++/** ++ * Set of timers per world, to track world specific timings. ++ */ ++// TODO: Re-implement missing timers ++public class WorldTimingsHandler { ++ public final Timing mobSpawn; ++ public final Timing doChunkUnload; ++ public final Timing doPortalForcer; ++ public final Timing scheduledBlocks; ++ public final Timing scheduledBlocksCleanup; ++ public final Timing scheduledBlocksTicking; ++ public final Timing chunkTicks; ++ public final Timing lightChunk; ++ public final Timing chunkTicksBlocks; ++ public final Timing doVillages; ++ public final Timing doChunkMap; ++ public final Timing doChunkMapUpdate; ++ public final Timing doChunkMapToUpdate; ++ public final Timing doChunkMapSortMissing; ++ public final Timing doChunkMapSortSendToPlayers; ++ public final Timing doChunkMapPlayersNeedingChunks; ++ public final Timing doChunkMapPendingSendToPlayers; ++ public final Timing doChunkMapUnloadChunks; ++ public final Timing doChunkGC; ++ public final Timing doSounds; ++ public final Timing entityRemoval; ++ public final Timing entityTick; ++ public final Timing tileEntityTick; ++ public final Timing tileEntityPending; ++ public final Timing tracker1; ++ public final Timing tracker2; ++ public final Timing doTick; ++ public final Timing tickEntities; ++ public final Timing chunks; ++ public final Timing newEntities; ++ public final Timing raids; ++ public final Timing chunkProviderTick; ++ public final Timing broadcastChunkUpdates; ++ public final Timing countNaturalMobs; ++ ++ public final Timing chunkLoad; ++ public final Timing chunkLoadPopulate; ++ public final Timing syncChunkLoad; ++ public final Timing chunkLoadLevelTimer; ++ public final Timing chunkIO; ++ public final Timing chunkPostLoad; ++ public final Timing worldSave; ++ public final Timing worldSaveChunks; ++ public final Timing worldSaveLevel; ++ public final Timing chunkSaveData; ++ ++ ++ public final Timing miscMobSpawning; ++ ++ public WorldTimingsHandler(World server) { ++ String name = ((WorldDataServer) server.getWorldData()).getName() + " - "; ++ ++ mobSpawn = Timings.ofSafe(name + "mobSpawn"); ++ doChunkUnload = Timings.ofSafe(name + "doChunkUnload"); ++ scheduledBlocks = Timings.ofSafe(name + "Scheduled Blocks"); ++ scheduledBlocksCleanup = Timings.ofSafe(name + "Scheduled Blocks - Cleanup"); ++ scheduledBlocksTicking = Timings.ofSafe(name + "Scheduled Blocks - Ticking"); ++ chunkTicks = Timings.ofSafe(name + "Chunk Ticks"); ++ lightChunk = Timings.ofSafe(name + "Light Chunk"); ++ chunkTicksBlocks = Timings.ofSafe(name + "Chunk Ticks - Blocks"); ++ doVillages = Timings.ofSafe(name + "doVillages"); ++ doChunkMap = Timings.ofSafe(name + "doChunkMap"); ++ doChunkMapUpdate = Timings.ofSafe(name + "doChunkMap - Update"); ++ doChunkMapToUpdate = Timings.ofSafe(name + "doChunkMap - To Update"); ++ doChunkMapSortMissing = Timings.ofSafe(name + "doChunkMap - Sort Missing"); ++ doChunkMapSortSendToPlayers = Timings.ofSafe(name + "doChunkMap - Sort Send To Players"); ++ doChunkMapPlayersNeedingChunks = Timings.ofSafe(name + "doChunkMap - Players Needing Chunks"); ++ doChunkMapPendingSendToPlayers = Timings.ofSafe(name + "doChunkMap - Pending Send To Players"); ++ doChunkMapUnloadChunks = Timings.ofSafe(name + "doChunkMap - Unload Chunks"); ++ doSounds = Timings.ofSafe(name + "doSounds"); ++ doChunkGC = Timings.ofSafe(name + "doChunkGC"); ++ doPortalForcer = Timings.ofSafe(name + "doPortalForcer"); ++ entityTick = Timings.ofSafe(name + "entityTick"); ++ entityRemoval = Timings.ofSafe(name + "entityRemoval"); ++ tileEntityTick = Timings.ofSafe(name + "tileEntityTick"); ++ tileEntityPending = Timings.ofSafe(name + "tileEntityPending"); ++ ++ chunkLoad = Timings.ofSafe(name + "Chunk Load"); ++ chunkLoadPopulate = Timings.ofSafe(name + "Chunk Load - Populate"); ++ syncChunkLoad = Timings.ofSafe(name + "Sync Chunk Load"); ++ chunkLoadLevelTimer = Timings.ofSafe(name + "Chunk Load - Load Level"); ++ chunkIO = Timings.ofSafe(name + "Chunk Load - DiskIO"); ++ chunkPostLoad = Timings.ofSafe(name + "Chunk Load - Post Load"); ++ worldSave = Timings.ofSafe(name + "World Save"); ++ worldSaveLevel = Timings.ofSafe(name + "World Save - Level"); ++ worldSaveChunks = Timings.ofSafe(name + "World Save - Chunks"); ++ chunkSaveData = Timings.ofSafe(name + "Chunk Save - Data"); ++ ++ tracker1 = Timings.ofSafe(name + "tracker stage 1"); ++ tracker2 = Timings.ofSafe(name + "tracker stage 2"); ++ doTick = Timings.ofSafe(name + "doTick"); ++ tickEntities = Timings.ofSafe(name + "tickEntities"); ++ ++ chunks = Timings.ofSafe(name + "Chunks"); ++ newEntities = Timings.ofSafe(name + "New entity registration"); ++ raids = Timings.ofSafe(name + "Raids"); ++ chunkProviderTick = Timings.ofSafe(name + "Chunk provider tick"); ++ broadcastChunkUpdates = Timings.ofSafe(name + "Broadcast chunk updates"); ++ countNaturalMobs = Timings.ofSafe(name + "Count natural mobs"); ++ ++ ++ miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc"); ++ } ++ ++ public static Timing getTickList(WorldServer worldserver, String timingsType) { ++ return Timings.ofSafe(((WorldDataServer) worldserver.getWorldData()).getName() + " - Scheduled " + timingsType); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index da922f395f0fff0881ead893c900c5b2623f48f0..1d03a79e9010bc514b72a81ba0ad4a62aeff1bb7 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -14,12 +14,15 @@ import java.util.concurrent.TimeUnit; + import java.util.logging.Level; + import java.util.regex.Pattern; + ++import com.google.common.collect.Lists; + import net.minecraft.server.MinecraftServer; + import org.bukkit.Bukkit; + import org.bukkit.command.Command; + import org.bukkit.configuration.ConfigurationSection; + import org.bukkit.configuration.InvalidConfigurationException; + import org.bukkit.configuration.file.YamlConfiguration; ++import co.aikar.timings.Timings; ++import co.aikar.timings.TimingsManager; + + public class PaperConfig { + +@@ -188,4 +191,30 @@ public class PaperConfig { + config.addDefault(path, def); + return config.getString(path, config.getString(path)); + } ++ ++ public static String timingsServerName; ++ private static void timings() { ++ boolean timings = getBoolean("timings.enabled", true); ++ boolean verboseTimings = getBoolean("timings.verbose", true); ++ TimingsManager.privacy = getBoolean("timings.server-name-privacy", false); ++ TimingsManager.hiddenConfigs = getList("timings.hidden-config-entries", Lists.newArrayList("database", "settings.bungeecord-addresses", "settings.velocity-support.secret")); ++ if (!TimingsManager.hiddenConfigs.contains("settings.velocity-support.secret")) { ++ TimingsManager.hiddenConfigs.add("settings.velocity-support.secret"); ++ } ++ int timingHistoryInterval = getInt("timings.history-interval", 300); ++ int timingHistoryLength = getInt("timings.history-length", 3600); ++ timingsServerName = getString("timings.server-name", "Unknown Server"); ++ ++ ++ Timings.setVerboseTimingsEnabled(verboseTimings); ++ Timings.setTimingsEnabled(timings); ++ Timings.setHistoryInterval(timingHistoryInterval * 20); ++ Timings.setHistoryLength(timingHistoryLength * 20); ++ ++ log("Timings: " + timings + ++ " - Verbose: " + verboseTimings + ++ " - Interval: " + timeSummary(Timings.getHistoryInterval() / 20) + ++ " - Length: " + timeSummary(Timings.getHistoryLength() / 20) + ++ " - Server Name: " + timingsServerName); ++ } + } +diff --git a/src/main/java/net/minecraft/commands/CustomFunction.java b/src/main/java/net/minecraft/commands/CustomFunction.java +index f96b132bb51c2d97703964a70fcb058f0649ac13..4c146ac041332230f6d9a01be28b6852c7624416 100644 +--- a/src/main/java/net/minecraft/commands/CustomFunction.java ++++ b/src/main/java/net/minecraft/commands/CustomFunction.java +@@ -15,12 +15,22 @@ public class CustomFunction { + + private final CustomFunction.c[] a; + private final MinecraftKey b; ++ // Paper start ++ public co.aikar.timings.Timing timing; ++ public co.aikar.timings.Timing getTiming() { ++ if (timing == null) { ++ timing = co.aikar.timings.MinecraftTimings.getCommandFunctionTiming(this); ++ } ++ return timing; ++ } ++ // Paper end + + public CustomFunction(MinecraftKey minecraftkey, CustomFunction.c[] acustomfunction_c) { + this.b = minecraftkey; + this.a = acustomfunction_c; + } + ++ public final MinecraftKey getMinecraftKey() { return this.a(); } // Paper - OBFHELPER + public MinecraftKey a() { + return this.b; + } +diff --git a/src/main/java/net/minecraft/network/protocol/PlayerConnectionUtils.java b/src/main/java/net/minecraft/network/protocol/PlayerConnectionUtils.java +index 989683265a99fc51607aa130733e00033b444a6a..e47da20ab8ce4da34755e105bf55d8542fb50138 100644 +--- a/src/main/java/net/minecraft/network/protocol/PlayerConnectionUtils.java ++++ b/src/main/java/net/minecraft/network/protocol/PlayerConnectionUtils.java +@@ -6,6 +6,8 @@ import net.minecraft.server.level.WorldServer; + import net.minecraft.util.thread.IAsyncTaskHandler; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import co.aikar.timings.MinecraftTimings; // Paper ++import co.aikar.timings.Timing; // Paper + + // CraftBukkit start + import net.minecraft.server.MinecraftServer; +@@ -22,10 +24,13 @@ public class PlayerConnectionUtils { + + public static void ensureMainThread(Packet packet, T t0, IAsyncTaskHandler iasynctaskhandler) throws CancelledPacketHandleException { + if (!iasynctaskhandler.isMainThread()) { ++ Timing timing = MinecraftTimings.getPacketTiming(packet); // Paper - timings + iasynctaskhandler.execute(() -> { + if (MinecraftServer.getServer().hasStopped() || (t0 instanceof PlayerConnection && ((PlayerConnection) t0).processedDisconnect)) return; // CraftBukkit, MC-142590 + if (t0.a().isConnected()) { ++ try (Timing ignored = timing.startTiming()) { // Paper - timings + packet.a(t0); ++ } // Paper - timings + } else { + PlayerConnectionUtils.LOGGER.debug("Ignoring packet due to disconnection: " + packet); + } +diff --git a/src/main/java/net/minecraft/server/CustomFunctionData.java b/src/main/java/net/minecraft/server/CustomFunctionData.java +index 21f5474a1cd60168ca059da542a432d40d88b514..07ca1234ad6ffa797003f7317fb88abf732bc159 100644 +--- a/src/main/java/net/minecraft/server/CustomFunctionData.java ++++ b/src/main/java/net/minecraft/server/CustomFunctionData.java +@@ -75,7 +75,7 @@ public class CustomFunctionData { + } else { + int j; + +- try { ++ try (co.aikar.timings.Timing timing = customfunction.getTiming().startTiming()) { // Paper + this.d = true; + int k = 0; + CustomFunction.c[] acustomfunction_c = customfunction.b(); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index cd3e7b8a23e40c717829bd262bfa675e4e3532f9..511d6094403d17522212fcdda6903a13517c44fa 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -174,7 +174,7 @@ import org.bukkit.craftbukkit.Main; + import org.bukkit.event.server.ServerLoadEvent; + // CraftBukkit end + +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot ++import co.aikar.timings.MinecraftTimings; // Paper + import org.spigotmc.SlackActivityAccountant; // Spigot + + public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant implements IMojangStatistics, ICommandListener, AutoCloseable { +@@ -228,8 +228,8 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { +- return !this.canSleepForTick(); ++ return !this.canSleepForTickNoOversleep(); // Paper - move oversleep into full server tick + }); + } + +@@ -1122,10 +1135,18 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { ++ return !this.canOversleep(); ++ }); ++ isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); ++ // Paper end ++ + ++this.ticks; + this.b(booleansupplier); + if (i - this.T >= 5000000000L) { +@@ -1143,14 +1164,12 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit +- SpigotTimings.worldSaveTimer.startTiming(); // Spigot + MinecraftServer.LOGGER.debug("Autosave started"); + this.methodProfiler.enter("save"); + this.playerList.savePlayers(); + this.saveChunks(true, false, false); + this.methodProfiler.exit(); + MinecraftServer.LOGGER.debug("Autosave finished"); +- SpigotTimings.worldSaveTimer.stopTiming(); // Spigot + } + + this.methodProfiler.enter("snooper"); +@@ -1163,6 +1182,13 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { + // CraftBukkit start - fire RemoteServerCommandEvent +@@ -680,10 +682,39 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + if (event.isCancelled()) { + return; + } ++ // Paper start ++ if (s.toLowerCase().startsWith("timings") && s.toLowerCase().matches("timings (report|paste|get|merged|seperate)")) { ++ org.bukkit.command.BufferedCommandSender sender = new org.bukkit.command.BufferedCommandSender(); ++ Waitable waitable = new Waitable() { ++ @Override ++ protected String evaluate() { ++ return sender.getBuffer(); ++ } ++ }; ++ waitableArray[0] = waitable; ++ co.aikar.timings.Timings.generateReport(new co.aikar.timings.TimingsReportListener(sender, waitable)); ++ } else { ++ // Paper end + ServerCommand serverCommand = new ServerCommand(event.getCommand(), remoteControlCommandListener.getWrapper()); + server.dispatchServerCommand(remoteConsole, serverCommand); ++ } // Paper + // CraftBukkit end + }); ++ // Paper start ++ if (waitableArray[0] != null) { ++ //noinspection unchecked ++ Waitable waitable = waitableArray[0]; ++ try { ++ return waitable.get(); ++ } catch (java.util.concurrent.ExecutionException e) { ++ throw new RuntimeException("Exception processing rcon command " + s, e.getCause()); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); // Maintain interrupted state ++ throw new RuntimeException("Interrupted processing rcon command " + s, e); ++ } ++ ++ } ++ // Paper end + return this.remoteControlCommandListener.getMessages(); + } + +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index a66ada96375a82b3c1834e8a4e0162e4faf4dfe9..cfb98fac55f38522dd4dcf78869db28984a6b8c6 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -344,11 +344,13 @@ public class ChunkProviderServer extends IChunkProvider { + } + + gameprofilerfiller.c("getChunkCacheMiss"); +- world.timings.syncChunkLoadTimer.startTiming(); // Spigot + CompletableFuture> completablefuture = this.getChunkFutureMainThread(i, j, chunkstatus, flag); + ++ if (!completablefuture.isDone()) { // Paper ++ this.world.timings.syncChunkLoad.startTiming(); // Paper + this.serverThreadQueue.awaitTasks(completablefuture::isDone); +- world.timings.syncChunkLoadTimer.stopTiming(); // Spigot ++ this.world.timings.syncChunkLoad.stopTiming(); // Paper ++ } // Paper + ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { + return ichunkaccess1; + }, (playerchunk_failure) -> { +@@ -536,7 +538,9 @@ public class ChunkProviderServer extends IChunkProvider { + + public void save(boolean flag) { + this.tickDistanceManager(); ++ try (co.aikar.timings.Timing timed = world.timings.chunkSaveData.startTiming()) { // Paper - Timings + this.playerChunkMap.save(flag); ++ } // Paper - Timings + } + + @Override +@@ -573,7 +577,9 @@ public class ChunkProviderServer extends IChunkProvider { + this.tickDistanceManager(); + this.world.timings.doChunkMap.stopTiming(); // Spigot + this.world.getMethodProfiler().exitEnter("chunks"); ++ this.world.timings.chunks.startTiming(); // Paper - timings + this.tickChunks(); ++ this.world.timings.chunks.stopTiming(); // Paper - timings + this.world.timings.doChunkUnload.startTiming(); // Spigot + this.world.getMethodProfiler().exitEnter("unload"); + this.playerChunkMap.unloadChunks(booleansupplier); +@@ -597,8 +603,10 @@ public class ChunkProviderServer extends IChunkProvider { + boolean flag2 = world.ticksPerAnimalSpawns != 0L && worlddata.getTime() % world.ticksPerAnimalSpawns == 0L; // CraftBukkit + + this.world.getMethodProfiler().enter("naturalSpawnCount"); ++ this.world.timings.countNaturalMobs.startTiming(); // Paper - timings + int l = this.chunkMapDistance.b(); + SpawnerCreature.d spawnercreature_d = SpawnerCreature.a(l, this.world.A(), this::a); ++ this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings + + this.p = spawnercreature_d; + this.world.getMethodProfiler().exit(); +@@ -609,7 +617,9 @@ public class ChunkProviderServer extends IChunkProvider { + + if (optional.isPresent()) { + this.world.getMethodProfiler().enter("broadcast"); ++ this.world.timings.broadcastChunkUpdates.startTiming(); // Paper - timings + playerchunk.a((Chunk) optional.get()); ++ this.world.timings.broadcastChunkUpdates.stopTiming(); // Paper - timings + this.world.getMethodProfiler().exit(); + Optional optional1 = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + +@@ -623,25 +633,25 @@ public class ChunkProviderServer extends IChunkProvider { + SpawnerCreature.a(this.world, chunk, spawnercreature_d, this.allowAnimals, this.allowMonsters, flag2); + } + +- this.world.timings.doTickTiles.startTiming(); // Spigot ++ this.world.timings.chunkTicks.startTiming(); // Spigot // Paper + this.world.a(chunk, k); +- this.world.timings.doTickTiles.stopTiming(); // Spigot ++ this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper + } + } + } + }); + this.world.getMethodProfiler().enter("customSpawners"); + if (flag1) { ++ try (co.aikar.timings.Timing ignored = this.world.timings.miscMobSpawning.startTiming()) { // Paper - timings + this.world.doMobSpawning(this.allowMonsters, this.allowAnimals); ++ } // Paper - timings + } + + this.world.getMethodProfiler().exit(); + this.world.getMethodProfiler().exit(); + } + +- this.world.timings.tracker.startTiming(); // Spigot + this.playerChunkMap.g(); +- this.world.timings.tracker.stopTiming(); // Spigot + } + + private void a(long i, Consumer consumer) { +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 0bf95b97140a67682ec2953ccc773f6faad7b7da..9eae9d7e9d18d73b1050e1d9b8859802cbd286ed 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -1,7 +1,9 @@ + package net.minecraft.server.level; + ++import co.aikar.timings.Timing; // Paper + import com.google.common.collect.ImmutableList; + import com.google.common.collect.Iterables; ++import com.google.common.collect.ComparisonChain; // Paper + import com.google.common.collect.Lists; + import com.google.common.collect.Queues; + import com.google.common.collect.Sets; +@@ -554,11 +556,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + private CompletableFuture> f(ChunkCoordIntPair chunkcoordintpair) { + return CompletableFuture.supplyAsync(() -> { +- try { ++ try (Timing ignored = this.world.timings.chunkLoad.startTimingIfSync()) { // Paper + this.world.getMethodProfiler().c("chunkLoad"); +- NBTTagCompound nbttagcompound = this.readChunkData(chunkcoordintpair); ++ NBTTagCompound nbttagcompound; // Paper ++ try (Timing ignored2 = this.world.timings.chunkIO.startTimingIfSync()) { // Paper start - timings ++ nbttagcompound = this.readChunkData(chunkcoordintpair); ++ } // Paper end + +- if (nbttagcompound != null) { ++ if (nbttagcompound != null) {try (Timing ignored2 = this.world.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings + boolean flag = nbttagcompound.hasKeyOfType("Level", 10) && nbttagcompound.getCompound("Level").hasKeyOfType("Status", 8); + + if (flag) { +@@ -570,7 +575,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + PlayerChunkMap.LOGGER.error("Chunk file at {} is missing level data, skipping", chunkcoordintpair); +- } ++ }} // Paper + } catch (ReportedException reportedexception) { + Throwable throwable = reportedexception.getCause(); + +@@ -607,7 +612,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return "chunkGenerate " + chunkstatus.d(); + }); + return completablefuture.thenComposeAsync((either) -> { +- return (CompletableFuture) either.map((list) -> { ++ return either.map((list) -> { // Paper - Shut up. + try { + CompletableFuture> completablefuture1 = chunkstatus.a(this.world, this.chunkGenerator, this.definedStructureManager, this.lightEngine, (ichunkaccess) -> { + return this.c(playerchunk); +@@ -660,6 +665,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + ChunkStatus chunkstatus = PlayerChunk.getChunkStatus(playerchunk.getTicketLevel()); + + return !chunkstatus.b(ChunkStatus.FULL) ? PlayerChunk.UNLOADED_CHUNK_ACCESS : either.mapLeft((ichunkaccess) -> { ++ try (Timing ignored = world.timings.chunkPostLoad.startTimingIfSync()) { // Paper + ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); + Chunk chunk; + +@@ -711,6 +717,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + return chunk; ++ } // Paper + }); + }, (runnable) -> { + Mailbox mailbox = this.mailboxMain; +@@ -1169,6 +1176,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + PlayerChunkMap.EntityTracker playerchunkmap_entitytracker; + ObjectIterator objectiterator; ++ world.timings.tracker1.startTiming(); // Paper + + for (objectiterator = this.trackedEntities.values().iterator(); objectiterator.hasNext(); playerchunkmap_entitytracker.trackerEntry.a()) { + playerchunkmap_entitytracker = (PlayerChunkMap.EntityTracker) objectiterator.next(); +@@ -1186,16 +1194,20 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + playerchunkmap_entitytracker.e = sectionposition1; + } + } ++ world.timings.tracker1.stopTiming(); // Paper + + if (!list.isEmpty()) { + objectiterator = this.trackedEntities.values().iterator(); + ++ world.timings.tracker2.startTiming(); // Paper + while (objectiterator.hasNext()) { + playerchunkmap_entitytracker = (PlayerChunkMap.EntityTracker) objectiterator.next(); + playerchunkmap_entitytracker.track(list); + } ++ world.timings.tracker2.stopTiming(); // Paper + } + ++ + } + + protected void broadcast(Entity entity, Packet packet) { +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index da53050c6c3d0a6ef3d5b96a88517e694536a268..4170743875d2fb16987e513713b2d141918219a5 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -2,6 +2,8 @@ package net.minecraft.server.level; + + import com.google.common.annotations.VisibleForTesting; + import com.google.common.collect.Iterables; ++import co.aikar.timings.TimingHistory; // Paper ++import co.aikar.timings.Timings; // Paper + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Queues; +@@ -153,7 +155,6 @@ import net.minecraft.world.level.block.ITileEntity; + import net.minecraft.world.level.storage.WorldDataServer; + import org.bukkit.Bukkit; + import org.bukkit.WeatherType; +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot + import org.bukkit.craftbukkit.event.CraftEventFactory; + import org.bukkit.craftbukkit.util.WorldUUID; + import org.bukkit.event.entity.CreatureSpawnEvent; +@@ -209,10 +210,10 @@ public class WorldServer extends World implements GeneratorAccessSeed { + // CraftBukkit end + this.nextTickListBlock = new TickListServer<>(this, (block) -> { + return block == null || block.getBlockData().isAir(); +- }, IRegistry.BLOCK::getKey, this::b); ++ }, IRegistry.BLOCK::getKey, this::b, "Blocks"); // Paper - Timings + this.nextTickListFluid = new TickListServer<>(this, (fluidtype) -> { + return fluidtype == null || fluidtype == FluidTypes.EMPTY; +- }, IRegistry.FLUID::getKey, this::a); ++ }, IRegistry.FLUID::getKey, this::a, "Fluids"); // Paper - Timings + this.navigators = Sets.newHashSet(); + this.L = new ObjectLinkedOpenHashSet(); + this.Q = flag1; +@@ -442,17 +443,21 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.Q(); + this.b(); + gameprofilerfiller.exitEnter("chunkSource"); ++ this.timings.chunkProviderTick.startTiming(); // Paper - timings + this.getChunkProvider().tick(booleansupplier); ++ this.timings.chunkProviderTick.stopTiming(); // Paper - timings + gameprofilerfiller.exitEnter("tickPending"); +- timings.doTickPending.startTiming(); // Spigot ++ timings.scheduledBlocks.startTiming(); // Paper + if (!this.isDebugWorld()) { + this.nextTickListBlock.b(); + this.nextTickListFluid.b(); + } +- timings.doTickPending.stopTiming(); // Spigot ++ timings.scheduledBlocks.stopTiming(); // Paper + + gameprofilerfiller.exitEnter("raid"); ++ this.timings.raids.startTiming(); // Paper - timings + this.persistentRaid.a(); ++ this.timings.raids.stopTiming(); // Paper - timings + gameprofilerfiller.exitEnter("blockEvents"); + timings.doSounds.startTiming(); // Spigot + this.ak(); +@@ -624,6 +629,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + gameprofilerfiller.exitEnter("tickBlocks"); ++ timings.chunkTicksBlocks.startTiming(); // Paper + if (i > 0) { + ChunkSection[] achunksection = chunk.getSections(); + int l = achunksection.length; +@@ -655,7 +661,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + } + } +- ++ timings.chunkTicksBlocks.stopTiming(); // Paper + gameprofilerfiller.exit(); + } + +@@ -753,14 +759,22 @@ public class WorldServer extends World implements GeneratorAccessSeed { + if (!(entity instanceof EntityHuman) && !this.getChunkProvider().a(entity)) { + this.chunkCheck(entity); + } else { ++ ++TimingHistory.entityTicks; // Paper - timings + // Spigot start ++ co.aikar.timings.Timing timer; // Paper + if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { + entity.ticksLived++; ++ timer = entity.getEntityType().inactiveTickTimer.startTiming(); try { // Paper - timings + entity.inactiveTick(); ++ } finally { timer.stopTiming(); } // Paper + return; + } + // Spigot end +- entity.tickTimer.startTiming(); // Spigot ++ // Paper start- timings ++ TimingHistory.activatedEntityTicks++; ++ timer = entity.getVehicle() != null ? entity.getEntityType().passengerTickTimer.startTiming() : entity.getEntityType().tickTimer.startTiming(); ++ try { ++ // Paper end - timings + entity.g(entity.locX(), entity.locY(), entity.locZ()); + entity.lastYaw = entity.yaw; + entity.lastPitch = entity.pitch; +@@ -787,7 +801,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.a(entity, entity1); + } + } +- entity.tickTimer.stopTiming(); // Spigot ++ } finally { timer.stopTiming(); } // Paper - timings + + } + } +@@ -865,6 +879,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + if (!flag1) { + org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit ++ try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper + if (iprogressupdate != null) { + iprogressupdate.a(new ChatMessage("menu.savingLevel")); + } +@@ -874,7 +889,10 @@ public class WorldServer extends World implements GeneratorAccessSeed { + iprogressupdate.c(new ChatMessage("menu.savingChunks")); + } + ++ timings.worldSaveChunks.startTiming(); // Paper + chunkproviderserver.save(flag); ++ timings.worldSaveChunks.stopTiming(); // Paper ++ } // Paper + } + + // CraftBukkit start - moved from MinecraftServer.saveChunks +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index bc9321e77c6b2f9ac17821413fa1357da2f26b05..4ed497ee04d9e9116e1f7d90bf975aeadd24aa93 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -211,6 +211,7 @@ import org.bukkit.inventory.EquipmentSlot; + import org.bukkit.inventory.InventoryView; + import org.bukkit.inventory.SmithingInventory; + import org.bukkit.util.NumberConversions; ++import co.aikar.timings.MinecraftTimings; // Paper + // CraftBukkit end + + public class PlayerConnection implements PacketListenerPlayIn { +@@ -293,7 +294,6 @@ public class PlayerConnection implements PacketListenerPlayIn { + // CraftBukkit end + + public void tick() { +- org.bukkit.craftbukkit.SpigotTimings.playerConnectionTimer.startTiming(); // Spigot + this.syncPosition(); + this.player.lastX = this.player.locX(); + this.player.lastY = this.player.locY(); +@@ -369,7 +369,6 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.player.resetIdleTimer(); // CraftBukkit - SPIGOT-854 + this.disconnect(new ChatMessage("multiplayer.disconnect.idling")); + } +- org.bukkit.craftbukkit.SpigotTimings.playerConnectionTimer.stopTiming(); // Spigot + + } + +@@ -1916,7 +1915,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + // CraftBukkit end + + private void handleCommand(String s) { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.startTiming(); // Spigot ++ MinecraftTimings.playerCommandTimer.startTiming(); // Paper + // CraftBukkit start - whole method + if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot + this.LOGGER.info(this.player.getName() + " issued server command: " + s); +@@ -1927,7 +1926,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot ++ MinecraftTimings.playerCommandTimer.stopTiming(); // Paper + return; + } + +@@ -1940,7 +1939,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + java.util.logging.Logger.getLogger(PlayerConnection.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + return; + } finally { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot ++ MinecraftTimings.playerCommandTimer.stopTiming(); // Paper + } + // this.minecraftServer.getCommandDispatcher().a(this.player.getCommandListener(), s); + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 5d1bbc42b4d65aa735570c53e4e6bc9e08899749..c601a5c577e438a3fa8dd4c5f36dbe9494b03d52 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.players; + ++import co.aikar.timings.MinecraftTimings; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -1022,10 +1023,11 @@ public abstract class PlayerList { + } + + public void savePlayers() { ++ MinecraftTimings.savePlayers.startTiming(); // Paper + for (int i = 0; i < this.players.size(); ++i) { + this.savePlayerFile((EntityPlayer) this.players.get(i)); + } +- ++ MinecraftTimings.savePlayers.stopTiming(); // Paper + } + + public WhiteList getWhitelist() { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 2bea2f4748cadf479dd4f89792ef5ffdd88e9cab..306f6c0db2333cce5dfc4bf1c09bfef05119a28b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -116,7 +116,6 @@ import org.bukkit.craftbukkit.event.CraftPortalEvent; + import org.bukkit.entity.Hanging; + import org.bukkit.entity.LivingEntity; + import org.bukkit.entity.Vehicle; +-import org.spigotmc.CustomTimingsHandler; // Spigot + import org.bukkit.event.entity.EntityCombustByEntityEvent; + import org.bukkit.event.hanging.HangingBreakByEntityEvent; + import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; +@@ -248,7 +247,6 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only + public boolean forceExplosionKnockback; // SPIGOT-949 + public boolean persistentInvisibility = false; +- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot + // Spigot start + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; +@@ -617,7 +615,6 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public void move(EnumMoveType enummovetype, Vec3D vec3d) { +- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.startTiming(); // Spigot + if (this.noclip) { + this.a(this.getBoundingBox().c(vec3d)); + this.recalcPosition(); +@@ -753,7 +750,6 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + this.world.getMethodProfiler().exit(); + } +- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.stopTiming(); // Spigot + } + + protected BlockPosition ap() { +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index c76ab1e6a54399eddae1ef2a595778385cd50026..661c3e2f12de36167bff149a3d979c4581402cbc 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -135,7 +135,7 @@ import org.bukkit.event.entity.EntityTeleportEvent; + import org.bukkit.event.player.PlayerItemConsumeEvent; + // CraftBukkit end + +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot ++import co.aikar.timings.MinecraftTimings; // Paper + + public abstract class EntityLiving extends Entity { + +@@ -2458,7 +2458,6 @@ public abstract class EntityLiving extends Entity { + + @Override + public void tick() { +- SpigotTimings.timerEntityBaseTick.startTiming(); // Spigot + super.tick(); + this.t(); + this.v(); +@@ -2507,9 +2506,7 @@ public abstract class EntityLiving extends Entity { + } + } + +- SpigotTimings.timerEntityBaseTick.stopTiming(); // Spigot + this.movementTick(); +- SpigotTimings.timerEntityTickRest.startTiming(); // Spigot + double d0 = this.locX() - this.lastX; + double d1 = this.locZ() - this.lastZ; + float f = (float) (d0 * d0 + d1 * d1); +@@ -2589,8 +2586,6 @@ public abstract class EntityLiving extends Entity { + if (this.isSleeping()) { + this.pitch = 0.0F; + } +- +- SpigotTimings.timerEntityTickRest.stopTiming(); // Spigot + } + + public void updateEquipment() { +@@ -2772,7 +2767,6 @@ public abstract class EntityLiving extends Entity { + + this.setMot(d4, d5, d6); + this.world.getMethodProfiler().enter("ai"); +- SpigotTimings.timerEntityAI.startTiming(); // Spigot + if (this.isFrozen()) { + this.jumping = false; + this.aR = 0.0F; +@@ -2782,7 +2776,6 @@ public abstract class EntityLiving extends Entity { + this.doTick(); + this.world.getMethodProfiler().exit(); + } +- SpigotTimings.timerEntityAI.stopTiming(); // Spigot + + this.world.getMethodProfiler().exit(); + this.world.getMethodProfiler().enter("jump"); +@@ -2817,9 +2810,9 @@ public abstract class EntityLiving extends Entity { + this.r(); + AxisAlignedBB axisalignedbb = this.getBoundingBox(); + +- SpigotTimings.timerEntityAIMove.startTiming(); // Spigot ++ // SpigotTimings.timerEntityAIMove.startTiming(); // Spigot // Paper + this.g(new Vec3D((double) this.aR, (double) this.aS, (double) this.aT)); +- SpigotTimings.timerEntityAIMove.stopTiming(); // Spigot ++ // SpigotTimings.timerEntityAIMove.stopTiming(); // Spigot // Paper + this.world.getMethodProfiler().exit(); + this.world.getMethodProfiler().enter("push"); + if (this.bf > 0) { +@@ -2827,9 +2820,7 @@ public abstract class EntityLiving extends Entity { + this.a(axisalignedbb, this.getBoundingBox()); + } + +- SpigotTimings.timerEntityAICollision.startTiming(); // Spigot + this.collideNearby(); +- SpigotTimings.timerEntityAICollision.stopTiming(); // Spigot + this.world.getMethodProfiler().exit(); + if (!this.world.isClientSide && this.dO() && this.aG()) { + this.damageEntity(DamageSource.DROWN, 1.0F); +diff --git a/src/main/java/net/minecraft/world/entity/EntityTypes.java b/src/main/java/net/minecraft/world/entity/EntityTypes.java +index dc92b112770955f9fa49a408262da2e5bbc4bf98..a707ba365e25ea15e2e9d22110696b6136aa0c6f 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityTypes.java ++++ b/src/main/java/net/minecraft/world/entity/EntityTypes.java +@@ -281,7 +281,9 @@ public class EntityTypes { + return IRegistry.ENTITY_TYPE.getOptional(MinecraftKey.a(s)); + } + +- public EntityTypes(EntityTypes.b entitytypes_b, EnumCreatureType enumcreaturetype, boolean flag, boolean flag1, boolean flag2, boolean flag3, ImmutableSet immutableset, EntitySize entitysize, int i, int j) { ++ public final String id; ++ public EntityTypes(EntityTypes.b entitytypes_b, EnumCreatureType enumcreaturetype, boolean flag, boolean flag1, boolean flag2, boolean flag3, ImmutableSet immutableset, EntitySize entitysize, int i, int j) { this(entitytypes_b, enumcreaturetype, flag, flag1, flag2, flag3, immutableset, entitysize, i, j, "custom"); } // Paper - old signature ++ public EntityTypes(EntityTypes.b entitytypes_b, EnumCreatureType enumcreaturetype, boolean flag, boolean flag1, boolean flag2, boolean flag3, ImmutableSet immutableset, EntitySize entitysize, int i, int j, String id) { // Paper - add id + this.bf = entitytypes_b; + this.bg = enumcreaturetype; + this.bl = flag3; +@@ -292,6 +294,14 @@ public class EntityTypes { + this.br = entitysize; + this.bm = i; + this.bn = j; ++ ++ // Paper start - timings ++ this.id = id; ++ this.tickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "tick"); ++ this.inactiveTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "inactiveTick"); ++ this.passengerTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "passengerTick"); ++ this.passengerInactiveTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "passengerInactiveTick"); ++ // Paper end + } + + @Nullable +@@ -512,6 +522,12 @@ public class EntityTypes { + return this.bn; + } + ++ // Paper start - timings ++ public final co.aikar.timings.Timing tickTimer; ++ public final co.aikar.timings.Timing inactiveTickTimer; ++ public final co.aikar.timings.Timing passengerTickTimer; ++ public final co.aikar.timings.Timing passengerInactiveTickTimer; ++ // Paper end + public boolean isDeltaTracking() { + return this != EntityTypes.PLAYER && this != EntityTypes.LLAMA_SPIT && this != EntityTypes.WITHER && this != EntityTypes.BAT && this != EntityTypes.ITEM_FRAME && this != EntityTypes.LEASH_KNOT && this != EntityTypes.PAINTING && this != EntityTypes.END_CRYSTAL && this != EntityTypes.EVOKER_FANGS; + } +@@ -599,7 +615,7 @@ public class EntityTypes { + SystemUtils.a(DataConverterTypes.ENTITY_TREE, s); + } + +- return new EntityTypes<>(this.a, this.b, this.d, this.e, this.f, this.g, this.c, this.j, this.h, this.i); ++ return new EntityTypes<>(this.a, this.b, this.d, this.e, this.f, this.g, this.c, this.j, this.h, this.i, s); // Paper - add id + } + } + +diff --git a/src/main/java/net/minecraft/world/level/TickListServer.java b/src/main/java/net/minecraft/world/level/TickListServer.java +index e7296b8684d6d8c2f256d4a9da87f408a198c331..c221e5caf518b8c588390e438346fa58fa8c5a38 100644 +--- a/src/main/java/net/minecraft/world/level/TickListServer.java ++++ b/src/main/java/net/minecraft/world/level/TickListServer.java +@@ -38,12 +38,17 @@ public class TickListServer implements TickList { + private final List> g = Lists.newArrayList(); + private final Consumer> h; + +- public TickListServer(WorldServer worldserver, Predicate predicate, Function function, Consumer> consumer) { ++ public TickListServer(WorldServer worldserver, Predicate predicate, Function function, Consumer> consumer, String timingsType) { // Paper + this.a = predicate; + this.b = function; + this.e = worldserver; + this.h = consumer; ++ this.timingCleanup = co.aikar.timings.WorldTimingsHandler.getTickList(worldserver, timingsType + " - Cleanup"); ++ this.timingTicking = co.aikar.timings.WorldTimingsHandler.getTickList(worldserver, timingsType + " - Ticking"); + } ++ private final co.aikar.timings.Timing timingCleanup; // Paper ++ private final co.aikar.timings.Timing timingTicking; // Paper ++ // Paper end + + public void b() { + int i = this.nextTickList.size(); +@@ -66,6 +71,7 @@ public class TickListServer implements TickList { + + this.e.getMethodProfiler().enter("cleaning"); + ++ this.timingCleanup.startTiming(); // Paper + NextTickListEntry nextticklistentry; + + while (i > 0 && iterator.hasNext()) { +@@ -81,7 +87,9 @@ public class TickListServer implements TickList { + --i; + } + } ++ this.timingCleanup.stopTiming(); // Paper + ++ this.timingTicking.startTiming(); // Paper + this.e.getMethodProfiler().exitEnter("ticking"); + + while ((nextticklistentry = (NextTickListEntry) this.f.poll()) != null) { +@@ -101,6 +109,7 @@ public class TickListServer implements TickList { + } + } + ++ this.timingTicking.stopTiming(); // Paper + this.e.getMethodProfiler().exit(); + this.g.clear(); + this.f.clear(); +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index f8df4f007a5bc24303b7278d734ab8890c0dd7bb..0f592e3c67f5b0e9c8035af6e2c34c03e2ed2e4c 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -70,7 +70,6 @@ import net.minecraft.server.level.WorldServer; + import net.minecraft.world.entity.item.EntityItem; + import net.minecraft.world.level.border.IWorldBorderListener; + import org.bukkit.Bukkit; +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.block.CapturedBlockState; +@@ -132,7 +131,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper + +- public final SpigotTimings.WorldTimingsHandler timings; // Spigot ++ public final co.aikar.timings.WorldTimingsHandler timings; // Paper + public static BlockPosition lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; +@@ -217,7 +216,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public void c(WorldBorder worldborder, double d0) {} + }); + // CraftBukkit end +- timings = new SpigotTimings.WorldTimingsHandler(this); // Spigot - code below can generate new world and access timings ++ timings = new co.aikar.timings.WorldTimingsHandler(this); // Paper - code below can generate new world and access timings + this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); + } +@@ -797,15 +796,14 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + } + + timings.tileEntityPending.stopTiming(); // Spigot ++ co.aikar.timings.TimingHistory.tileEntityTicks += this.tileEntityListTick.size(); // Paper + gameprofilerfiller.exit(); + spigotConfig.currentPrimedTnt = 0; // Spigot + } + + public void a(Consumer consumer, Entity entity) { + try { +- SpigotTimings.tickEntityTimer.startTiming(); // Spigot + consumer.accept(entity); +- SpigotTimings.tickEntityTimer.stopTiming(); // Spigot + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.a(throwable, "Ticking entity"); + CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity being ticked"); +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 bab55395ba92d0f3788e798ae0e154d62c4ec2fa..d285c4e3d9f938973bf7fb904680044b414e6236 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -61,6 +61,15 @@ public class Block extends BlockBase implements IMaterial { + }); + protected final BlockStateList blockStateList; + private IBlockData blockData; ++ // Paper start ++ public co.aikar.timings.Timing timing; ++ public co.aikar.timings.Timing getTiming() { ++ if (timing == null) { ++ timing = co.aikar.timings.MinecraftTimings.getBlockTiming(this); ++ } ++ return timing; ++ } ++ // Paper end + @Nullable + private String name; + @Nullable +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +index 75110c41af3e0097aef65091a2497dd87d08b4b2..9ebd91e1309938f81583eb3d4dd97fd39bcc930a 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +@@ -23,10 +23,12 @@ import org.bukkit.inventory.InventoryHolder; + // CraftBukkit end + + import org.spigotmc.CustomTimingsHandler; // Spigot ++import co.aikar.timings.MinecraftTimings; // Paper ++import co.aikar.timings.Timing; // Paper + + public abstract class TileEntity implements net.minecraft.server.KeyedObject { // Paper + +- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot ++ public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper + // CraftBukkit start - data containers + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + public CraftPersistentDataContainer persistentDataContainer; +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index acdcece38a4b30d6c89eb4342918ae8997db9f0b..ac576d268b23148089d404cb22d8c2f9d1a79d6e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -733,6 +733,7 @@ public class Chunk implements IChunkAccess { + server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(this.bukkitChunk, this.needsDecoration)); + + if (this.needsDecoration) { ++ try (co.aikar.timings.Timing ignored = this.world.timings.chunkLoadPopulate.startTiming()) { // Paper + this.needsDecoration = false; + java.util.Random random = new java.util.Random(); + random.setSeed(world.getSeed()); +@@ -752,6 +753,7 @@ public class Chunk implements IChunkAccess { + } + } + server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk)); ++ } // Paper + } + } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +index 27703b807735d52313b93f8f606aa263571525d2..f301c7ba4b17b92c6cf2fcee6da1e67081dad4fa 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.level.chunk.storage; + ++import co.aikar.timings.Timings; + import com.google.common.collect.Maps; + import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + import it.unimi.dsi.fastutil.longs.LongSet; +@@ -446,7 +447,6 @@ public class ChunkRegionLoader { + private static void loadEntities(NBTTagCompound nbttagcompound, Chunk chunk) { + NBTTagList nbttaglist = nbttagcompound.getList("Entities", 10); + World world = chunk.getWorld(); +- world.timings.syncChunkLoadEntitiesTimer.startTiming(); // Spigot + + for (int i = 0; i < nbttaglist.size(); ++i) { + NBTTagCompound nbttagcompound1 = nbttaglist.getCompound(i); +@@ -458,8 +458,6 @@ public class ChunkRegionLoader { + chunk.d(true); + } + +- world.timings.syncChunkLoadEntitiesTimer.stopTiming(); // Spigot +- world.timings.syncChunkLoadTileEntitiesTimer.startTiming(); // Spigot + NBTTagList nbttaglist1 = nbttagcompound.getList("TileEntities", 10); + + for (int j = 0; j < nbttaglist1.size(); ++j) { +@@ -477,8 +475,6 @@ public class ChunkRegionLoader { + } + } + } +- world.timings.syncChunkLoadTileEntitiesTimer.stopTiming(); // Spigot +- + } + + private static NBTTagCompound a(ChunkCoordIntPair chunkcoordintpair, Map, StructureStart> map, Map, LongSet> map1) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index bd62288b6951c6b5962497a3c93989cab5d66ad4..6d68610d8ffdef4357cf61ac2f4cf6bf54157b15 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2069,12 +2069,31 @@ public final class CraftServer implements Server { + private final org.bukkit.Server.Spigot spigot = new org.bukkit.Server.Spigot() + { + ++ @Deprecated + @Override + public YamlConfiguration getConfig() + { + return org.spigotmc.SpigotConfig.config; + } + ++ @Override ++ public YamlConfiguration getBukkitConfig() ++ { ++ return configuration; ++ } ++ ++ @Override ++ public YamlConfiguration getSpigotConfig() ++ { ++ return org.spigotmc.SpigotConfig.config; ++ } ++ ++ @Override ++ public YamlConfiguration getPaperConfig() ++ { ++ return com.destroystokyo.paper.PaperConfig.config; ++ } ++ + @Override + public void restart() { + org.spigotmc.RestartCommand.restart(); +diff --git a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java +index ebf2c62e9ea126577a6cbcbbeb3f3aba259a1f63..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 +--- a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java ++++ b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java +@@ -1,163 +0,0 @@ +-package org.bukkit.craftbukkit; +- +-import java.util.HashMap; +-import net.minecraft.world.entity.Entity; +-import net.minecraft.world.level.World; +-import net.minecraft.world.level.block.entity.TileEntity; +-import net.minecraft.world.level.storage.WorldDataServer; +-import org.bukkit.craftbukkit.scheduler.CraftTask; +-import org.bukkit.plugin.java.JavaPluginLoader; +-import org.bukkit.scheduler.BukkitTask; +-import org.spigotmc.CustomTimingsHandler; +- +-public class SpigotTimings { +- +- public static final CustomTimingsHandler serverTickTimer = new CustomTimingsHandler("** Full Server Tick"); +- public static final CustomTimingsHandler playerListTimer = new CustomTimingsHandler("Player List"); +- public static final CustomTimingsHandler commandFunctionsTimer = new CustomTimingsHandler("Command Functions"); +- public static final CustomTimingsHandler connectionTimer = new CustomTimingsHandler("Connection Handler"); +- public static final CustomTimingsHandler playerConnectionTimer = new CustomTimingsHandler("** PlayerConnection"); +- public static final CustomTimingsHandler tickablesTimer = new CustomTimingsHandler("Tickables"); +- public static final CustomTimingsHandler schedulerTimer = new CustomTimingsHandler("Scheduler"); +- public static final CustomTimingsHandler timeUpdateTimer = new CustomTimingsHandler("Time Update"); +- public static final CustomTimingsHandler serverCommandTimer = new CustomTimingsHandler("Server Command"); +- public static final CustomTimingsHandler worldSaveTimer = new CustomTimingsHandler("World Save"); +- +- public static final CustomTimingsHandler entityMoveTimer = new CustomTimingsHandler("** entityMove"); +- public static final CustomTimingsHandler tickEntityTimer = new CustomTimingsHandler("** tickEntity"); +- public static final CustomTimingsHandler activatedEntityTimer = new CustomTimingsHandler("** activatedTickEntity"); +- public static final CustomTimingsHandler tickTileEntityTimer = new CustomTimingsHandler("** tickTileEntity"); +- +- public static final CustomTimingsHandler timerEntityBaseTick = new CustomTimingsHandler("** livingEntityBaseTick"); +- public static final CustomTimingsHandler timerEntityAI = new CustomTimingsHandler("** livingEntityAI"); +- public static final CustomTimingsHandler timerEntityAICollision = new CustomTimingsHandler("** livingEntityAICollision"); +- public static final CustomTimingsHandler timerEntityAIMove = new CustomTimingsHandler("** livingEntityAIMove"); +- public static final CustomTimingsHandler timerEntityTickRest = new CustomTimingsHandler("** livingEntityTickRest"); +- +- public static final CustomTimingsHandler processQueueTimer = new CustomTimingsHandler("processQueue"); +- public static final CustomTimingsHandler schedulerSyncTimer = new CustomTimingsHandler("** Scheduler - Sync Tasks", JavaPluginLoader.pluginParentTimer); +- +- public static final CustomTimingsHandler playerCommandTimer = new CustomTimingsHandler("** playerCommand"); +- +- public static final CustomTimingsHandler entityActivationCheckTimer = new CustomTimingsHandler("entityActivationCheck"); +- public static final CustomTimingsHandler checkIfActiveTimer = new CustomTimingsHandler("** checkIfActive"); +- +- public static final HashMap entityTypeTimingMap = new HashMap(); +- public static final HashMap tileEntityTypeTimingMap = new HashMap(); +- public static final HashMap pluginTaskTimingMap = new HashMap(); +- +- /** +- * Gets a timer associated with a plugins tasks. +- * @param task +- * @param period +- * @return +- */ +- public static CustomTimingsHandler getPluginTaskTimings(BukkitTask task, long period) { +- if (!task.isSync()) { +- return null; +- } +- String plugin; +- final CraftTask ctask = (CraftTask) task; +- +- if (task.getOwner() != null) { +- plugin = task.getOwner().getDescription().getFullName(); +- } else { +- plugin = "Unknown"; +- } +- String taskname = ctask.getTaskName(); +- +- String name = "Task: " + plugin + " Runnable: " + taskname; +- if (period > 0) { +- name += "(interval:" + period + ")"; +- } else { +- name += "(Single)"; +- } +- CustomTimingsHandler result = pluginTaskTimingMap.get(name); +- if (result == null) { +- result = new CustomTimingsHandler(name, SpigotTimings.schedulerSyncTimer); +- pluginTaskTimingMap.put(name, result); +- } +- return result; +- } +- +- /** +- * Get a named timer for the specified entity type to track type specific timings. +- * @param entity +- * @return +- */ +- public static CustomTimingsHandler getEntityTimings(Entity entity) { +- String entityType = entity.getClass().getName(); +- CustomTimingsHandler result = entityTypeTimingMap.get(entityType); +- if (result == null) { +- result = new CustomTimingsHandler("** tickEntity - " + entity.getClass().getSimpleName(), activatedEntityTimer); +- entityTypeTimingMap.put(entityType, result); +- } +- return result; +- } +- +- /** +- * Get a named timer for the specified tile entity type to track type specific timings. +- * @param entity +- * @return +- */ +- public static CustomTimingsHandler getTileEntityTimings(TileEntity entity) { +- String entityType = entity.getClass().getName(); +- CustomTimingsHandler result = tileEntityTypeTimingMap.get(entityType); +- if (result == null) { +- result = new CustomTimingsHandler("** tickTileEntity - " + entity.getClass().getSimpleName(), tickTileEntityTimer); +- tileEntityTypeTimingMap.put(entityType, result); +- } +- return result; +- } +- +- /** +- * Set of timers per world, to track world specific timings. +- */ +- public static class WorldTimingsHandler { +- public final CustomTimingsHandler mobSpawn; +- public final CustomTimingsHandler doChunkUnload; +- public final CustomTimingsHandler doTickPending; +- public final CustomTimingsHandler doTickTiles; +- public final CustomTimingsHandler doChunkMap; +- public final CustomTimingsHandler doSounds; +- public final CustomTimingsHandler entityTick; +- public final CustomTimingsHandler tileEntityTick; +- public final CustomTimingsHandler tileEntityPending; +- public final CustomTimingsHandler tracker; +- public final CustomTimingsHandler doTick; +- public final CustomTimingsHandler tickEntities; +- +- public final CustomTimingsHandler syncChunkLoadTimer; +- public final CustomTimingsHandler syncChunkLoadStructuresTimer; +- public final CustomTimingsHandler syncChunkLoadEntitiesTimer; +- public final CustomTimingsHandler syncChunkLoadTileEntitiesTimer; +- public final CustomTimingsHandler syncChunkLoadTileTicksTimer; +- public final CustomTimingsHandler syncChunkLoadPostTimer; +- +- public WorldTimingsHandler(World server) { +- String name = ((WorldDataServer) server.worldData).getName() + " - "; +- +- mobSpawn = new CustomTimingsHandler("** " + name + "mobSpawn"); +- doChunkUnload = new CustomTimingsHandler("** " + name + "doChunkUnload"); +- doTickPending = new CustomTimingsHandler("** " + name + "doTickPending"); +- doTickTiles = new CustomTimingsHandler("** " + name + "doTickTiles"); +- doChunkMap = new CustomTimingsHandler("** " + name + "doChunkMap"); +- doSounds = new CustomTimingsHandler("** " + name + "doSounds"); +- entityTick = new CustomTimingsHandler("** " + name + "entityTick"); +- tileEntityTick = new CustomTimingsHandler("** " + name + "tileEntityTick"); +- tileEntityPending = new CustomTimingsHandler("** " + name + "tileEntityPending"); +- +- syncChunkLoadTimer = new CustomTimingsHandler("** " + name + "syncChunkLoad"); +- syncChunkLoadStructuresTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Structures"); +- syncChunkLoadEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Entities"); +- syncChunkLoadTileEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileEntities"); +- syncChunkLoadTileTicksTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileTicks"); +- syncChunkLoadPostTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Post"); +- +- +- tracker = new CustomTimingsHandler(name + "tracker"); +- doTick = new CustomTimingsHandler(name + "doTick"); +- tickEntities = new CustomTimingsHandler(name + "tickEntities"); +- } +- } +-} +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 97387235e883fd53950a88c244ac1bc9b24ec225..597b3b061c707081e7665d5896f5d73676e691d6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1803,6 +1803,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + packet.components = components; + getHandle().playerConnection.sendPacket(packet); + } ++ ++ // Paper start ++ @Override ++ public int getPing() ++ { ++ return getHandle().ping; ++ } ++ // Paper end + }; + + public Player.Spigot spigot() +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 4bf48f77f3f7cd62a91590543f5af441c8268029..ffe9cc1011226d604dc5499e7692e9a9a5132b72 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit.scheduler; + ++import co.aikar.timings.MinecraftTimings; // Paper + import com.google.common.util.concurrent.ThreadFactoryBuilder; + import java.util.ArrayList; + import java.util.Comparator; +@@ -179,7 +180,8 @@ public class CraftScheduler implements BukkitScheduler { + } + + public BukkitTask scheduleInternalTask(Runnable run, int delay, String taskName) { +- final CraftTask task = new CraftTask(run, nextId(), taskName); ++ final CraftTask task = new CraftTask(run, nextId(), "Internal - " + (taskName != null ? taskName : "Unknown")); ++ task.internal = true; + return handle(task, delay); + } + +@@ -260,7 +262,7 @@ public class CraftScheduler implements BukkitScheduler { + } + return false; + } +- }); ++ }){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer();}}; // Paper + handle(task, 0L); + for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { +@@ -295,7 +297,7 @@ public class CraftScheduler implements BukkitScheduler { + } + } + } +- }); ++ }){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer(plugin);}}; // Paper + handle(task, 0L); + for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { +@@ -402,9 +404,7 @@ public class CraftScheduler implements BukkitScheduler { + if (task.isSync()) { + currentTask = task; + try { +- task.timings.startTiming(); // Spigot + task.run(); +- task.timings.stopTiming(); // Spigot + } catch (final Throwable throwable) { + // Paper start + String msg = String.format( +@@ -438,8 +438,10 @@ public class CraftScheduler implements BukkitScheduler { + runners.remove(task.getTaskId()); + } + } ++ MinecraftTimings.bukkitSchedulerFinishTimer.startTiming(); + pending.addAll(temp); + temp.clear(); ++ MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming(); + debugHead = debugHead.getNextHead(currentTick); + } + +@@ -472,6 +474,7 @@ public class CraftScheduler implements BukkitScheduler { + } + + private void parsePending() { ++ MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); + CraftTask head = this.head; + CraftTask task = head.getNext(); + CraftTask lastTask = head; +@@ -490,6 +493,7 @@ public class CraftScheduler implements BukkitScheduler { + task.setNext(null); + } + this.head = lastTask; ++ MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); + } + + private boolean isReady(final int currentTick) { +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +index 09aa6809c5400ce8548ac902908b750ce7c964ec..3c96807e97657502849093e4371e9fef3584a346 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +@@ -1,12 +1,15 @@ + package org.bukkit.craftbukkit.scheduler; + + import java.util.function.Consumer; ++ ++import co.aikar.timings.NullTimingHandler; + import org.bukkit.Bukkit; + import org.bukkit.plugin.Plugin; + import org.bukkit.scheduler.BukkitTask; + +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot + import org.spigotmc.CustomTimingsHandler; // Spigot ++import co.aikar.timings.MinecraftTimings; // Paper ++import co.aikar.timings.Timing; // Paper + + public class CraftTask implements BukkitTask, Runnable { // Spigot + +@@ -26,12 +29,12 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + */ + private volatile long period; + private long nextRun; +- private final Runnable rTask; +- private final Consumer cTask; ++ public final Runnable rTask; // Paper ++ public final Consumer cTask; // Paper ++ public Timing timings; // Paper + private final Plugin plugin; + private final int id; + +- final CustomTimingsHandler timings; // Spigot + CraftTask() { + this(null, null, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING); + } +@@ -51,7 +54,7 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + this.id = id; + this.period = CraftTask.NO_REPEATING; + this.taskName = taskName; +- this.timings = null; // Will be changed in later patch ++ this.timings = MinecraftTimings.getInternalTaskName(taskName); + } + // Paper end + +@@ -72,7 +75,7 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + } + this.id = id; + this.period = period; +- this.timings = this.isSync() ? SpigotTimings.getPluginTaskTimings(this, period) : null; // Spigot ++ timings = task != null ? MinecraftTimings.getPluginTaskTimings(this, period) : NullTimingHandler.NULL; // Paper + } + + @Override +@@ -92,11 +95,13 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + + @Override + public void run() { ++ try (Timing ignored = timings.startTiming()) { // Paper + if (rTask != null) { + rTask.run(); + } else { + cTask.accept(this); + } ++ } // Paper + } + + long getPeriod() { +@@ -123,7 +128,7 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + this.next = next; + } + +- Class getTaskClass() { ++ public Class getTaskClass() { + return (rTask != null) ? rTask.getClass() : ((cTask != null) ? cTask.getClass() : null); + } + +@@ -147,9 +152,4 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + return true; + } + +- // Spigot start +- public String getTaskName() { +- return (getTaskClass() == null) ? "Unknown" : getTaskClass().getName(); +- } +- // Spigot end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java +index e52ef47b783785dc214746b678e7b549aea9a274..3d90b3426873a3528af14f7f1ab0adae0027da2e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java +@@ -5,6 +5,7 @@ import org.bukkit.util.CachedServerIcon; + public class CraftIconCache implements CachedServerIcon { + public final String value; + ++ public String getData() { return value; } // Paper + public CraftIconCache(final String value) { + this.value = value; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 972840fd32cd4b6cb73b7f97d06dcd5699c28ba4..65131f0977fa55c4761c34ce52720170feb61a72 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -158,6 +158,12 @@ public final class CraftMagicNumbers implements UnsafeValues { + return CraftNamespacedKey.toMinecraft(mat.getKey()); + } + // ======================================================================== ++ // Paper start ++ @Override ++ public void reportTimings() { ++ co.aikar.timings.TimingsExport.reportTimings(); ++ } ++ // Paper end + + public static byte toLegacyData(IBlockData data) { + return CraftLegacy.toLegacyData(data); +@@ -332,6 +338,13 @@ public final class CraftMagicNumbers implements UnsafeValues { + return clazz; + } + ++ // Paper start ++ @Override ++ public String getTimingsServerName() { ++ return com.destroystokyo.paper.PaperConfig.timingsServerName; ++ } ++ // 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 44a7323ffbdf91f8e0672e0fadf0d3d26ed9a290..69c5d4e51ebf747d931fadc819973e36f001f5bc 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -29,7 +29,7 @@ import net.minecraft.world.entity.raid.EntityRaider; + import net.minecraft.world.level.World; + import net.minecraft.world.level.chunk.Chunk; + import net.minecraft.world.phys.AxisAlignedBB; +-import org.bukkit.craftbukkit.SpigotTimings; ++import co.aikar.timings.MinecraftTimings; + + public class ActivationRange + { +@@ -73,8 +73,8 @@ public class ActivationRange + /** + * These entities are excluded from Activation range checks. + * +- * @param entity +- * @param config ++ * @param entity Entity to initialize ++ * @param config Spigot config to determine ranges + * @return boolean If it should always tick. + */ + public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) +@@ -109,7 +109,7 @@ public class ActivationRange + */ + public static void activateEntities(World world) + { +- SpigotTimings.entityActivationCheckTimer.startTiming(); ++ MinecraftTimings.entityActivationCheckTimer.startTiming(); + final int miscActivationRange = world.spigotConfig.miscActivationRange; + final int raiderActivationRange = world.spigotConfig.raiderActivationRange; + final int animalActivationRange = world.spigotConfig.animalActivationRange; +@@ -146,7 +146,7 @@ public class ActivationRange + } + } + } +- SpigotTimings.entityActivationCheckTimer.stopTiming(); ++ MinecraftTimings.entityActivationCheckTimer.stopTiming(); + } + + /** +@@ -243,10 +243,8 @@ public class ActivationRange + */ + public static boolean checkIfActive(Entity entity) + { +- SpigotTimings.checkIfActiveTimer.startTiming(); + // Never safe to skip fireworks or entities not yet added to chunk + if ( !entity.inChunk || entity instanceof EntityFireworks ) { +- SpigotTimings.checkIfActiveTimer.stopTiming(); + return true; + } + +@@ -270,7 +268,6 @@ public class ActivationRange + { + isActive = false; + } +- SpigotTimings.checkIfActiveTimer.stopTiming(); + return isActive; + } + } diff --git a/patches/server-unmapped/0001/0011-Adventure.patch b/patches/server-unmapped/0001/0011-Adventure.patch new file mode 100644 index 0000000000..1bb5b2a1c5 --- /dev/null +++ b/patches/server-unmapped/0001/0011-Adventure.patch @@ -0,0 +1,3183 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Riley Park +Date: Fri, 29 Jan 2021 17:54:03 +0100 +Subject: [PATCH] Adventure + +Co-authored-by: zml +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 1d03a79e9010bc514b72a81ba0ad4a62aeff1bb7..429b74474ced04d8dd8f038b8590b8dfe178bf4d 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -217,4 +217,9 @@ public class PaperConfig { + " - Length: " + timeSummary(Timings.getHistoryLength() / 20) + + " - Server Name: " + timingsServerName); + } ++ ++ public static boolean useDisplayNameInQuit = false; ++ private static void useDisplayNameInQuit() { ++ useDisplayNameInQuit = getBoolean("use-display-name-in-quit-message", useDisplayNameInQuit); ++ } + } +diff --git a/src/main/java/io/papermc/paper/adventure/AdventureComponent.java b/src/main/java/io/papermc/paper/adventure/AdventureComponent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..89597b4a3064c3c6001c7e927a848ee73a1b1fd9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/AdventureComponent.java +@@ -0,0 +1,77 @@ ++package io.papermc.paper.adventure; ++ ++import com.google.gson.JsonElement; ++import com.google.gson.JsonSerializationContext; ++import com.google.gson.JsonSerializer; ++import java.lang.reflect.Type; ++import java.util.List; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.TextComponent; ++import net.minecraft.network.chat.ChatModifier; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.network.chat.IChatMutableComponent; ++import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++public final class AdventureComponent implements IChatBaseComponent { ++ final Component wrapped; ++ private @MonotonicNonNull IChatBaseComponent converted; ++ ++ public AdventureComponent(final Component wrapped) { ++ this.wrapped = wrapped; ++ } ++ ++ public IChatBaseComponent deepConverted() { ++ IChatBaseComponent converted = this.converted; ++ if (converted == null) { ++ converted = PaperAdventure.WRAPPER_AWARE_SERIALIZER.serialize(this.wrapped); ++ this.converted = converted; ++ } ++ return converted; ++ } ++ ++ public @Nullable IChatBaseComponent deepConvertedIfPresent() { ++ return this.converted; ++ } ++ ++ @Override ++ public ChatModifier getChatModifier() { ++ return this.deepConverted().getChatModifier(); ++ } ++ ++ @Override ++ public String getText() { ++ if (this.wrapped instanceof TextComponent) { ++ return ((TextComponent) this.wrapped).content(); ++ } else { ++ return this.deepConverted().getText(); ++ } ++ } ++ ++ @Override ++ public String getString() { ++ return PaperAdventure.PLAIN.serialize(this.wrapped); ++ } ++ ++ @Override ++ public List getSiblings() { ++ return this.deepConverted().getSiblings(); ++ } ++ ++ @Override ++ public IChatMutableComponent g() { ++ return this.deepConverted().g(); ++ } ++ ++ @Override ++ public IChatMutableComponent mutableCopy() { ++ return this.deepConverted().mutableCopy(); ++ } ++ ++ public static class Serializer implements JsonSerializer { ++ @Override ++ public JsonElement serialize(final AdventureComponent src, final Type type, final JsonSerializationContext context) { ++ return PaperAdventure.GSON.serializer().toJsonTree(src.wrapped, Component.class); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0cb9368dcffe08a1ab004c6e2803b43eb655ac43 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +@@ -0,0 +1,215 @@ ++package io.papermc.paper.adventure; ++ ++import io.papermc.paper.chat.ChatComposer; ++import io.papermc.paper.event.player.AbstractChatEvent; ++import io.papermc.paper.event.player.AsyncChatEvent; ++import io.papermc.paper.event.player.ChatEvent; ++import java.util.Set; ++import java.util.concurrent.ExecutionException; ++import java.util.function.Consumer; ++import java.util.regex.Pattern; ++ ++import net.kyori.adventure.audience.MessageType; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.TextReplacementConfig; ++import net.kyori.adventure.text.event.ClickEvent; ++import net.minecraft.network.chat.ChatMessageType; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.EntityPlayer; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.util.LazyPlayerSet; ++import org.bukkit.craftbukkit.util.Waitable; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Event; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.player.AsyncPlayerChatEvent; ++import org.bukkit.event.player.PlayerChatEvent; ++ ++public final class ChatProcessor { ++ // <-- copied from adventure-text-serializer-legacy ++ private static final Pattern DEFAULT_URL_PATTERN = Pattern.compile("(?:(https?)://)?([-\\w_.]+\\.\\w{2,})(/\\S*)?"); ++ private static final Pattern URL_SCHEME_PATTERN = Pattern.compile("^[a-z][a-z0-9+\\-.]*:"); ++ private static final TextReplacementConfig URL_REPLACEMENT_CONFIG = TextReplacementConfig.builder() ++ .match(DEFAULT_URL_PATTERN) ++ .replacement(url -> { ++ String clickUrl = url.content(); ++ if (!URL_SCHEME_PATTERN.matcher(clickUrl).find()) { ++ clickUrl = "http://" + clickUrl; ++ } ++ return url.clickEvent(ClickEvent.openUrl(clickUrl)); ++ }) ++ .build(); ++ // copied from adventure-text-serializer-legacy --> ++ final MinecraftServer server; ++ final EntityPlayer player; ++ final String message; ++ final boolean async; ++ ++ public ChatProcessor(final MinecraftServer server, final EntityPlayer player, final String message, final boolean async) { ++ this.server = server; ++ this.player = player; ++ this.message = message; ++ this.async = async; ++ } ++ ++ @SuppressWarnings({"CodeBlock2Expr", "deprecated"}) ++ public void process() { ++ this.processingLegacyFirst( ++ // continuing from AsyncPlayerChatEvent (without PlayerChatEvent) ++ event -> { ++ this.processModern( ++ legacyComposer(event.getFormat()), ++ event.getRecipients(), ++ PaperAdventure.LEGACY_SECTION_UXRC.deserialize(event.getMessage()), ++ event.isCancelled() ++ ); ++ }, ++ // continuing from AsyncPlayerChatEvent and PlayerChatEvent ++ event -> { ++ this.processModern( ++ legacyComposer(event.getFormat()), ++ event.getRecipients(), ++ PaperAdventure.LEGACY_SECTION_UXRC.deserialize(event.getMessage()), ++ event.isCancelled() ++ ); ++ }, ++ // no legacy events called, all nice and fresh! ++ () -> { ++ this.processModern( ++ ChatComposer.DEFAULT, ++ new LazyPlayerSet(this.server), ++ Component.text(this.message).replaceText(URL_REPLACEMENT_CONFIG), ++ false ++ ); ++ } ++ ); ++ } ++ ++ @SuppressWarnings("deprecation") ++ private void processingLegacyFirst( ++ final Consumer continueAfterAsync, ++ final Consumer continueAfterAsyncAndSync, ++ final Runnable modernOnly ++ ) { ++ final boolean listenersOnAsyncEvent = anyListeners(AsyncPlayerChatEvent.getHandlerList()); ++ final boolean listenersOnSyncEvent = anyListeners(PlayerChatEvent.getHandlerList()); ++ if (listenersOnAsyncEvent || listenersOnSyncEvent) { ++ final CraftPlayer player = this.player.getBukkitEntity(); ++ final AsyncPlayerChatEvent ae = new AsyncPlayerChatEvent(this.async, player, this.message, new LazyPlayerSet(this.server)); ++ post(ae); ++ if (listenersOnSyncEvent) { ++ final PlayerChatEvent se = new PlayerChatEvent(player, ae.getMessage(), ae.getFormat(), ae.getRecipients()); ++ se.setCancelled(ae.isCancelled()); // propagate cancelled state ++ this.queueIfAsyncOrRunImmediately(new Waitable() { ++ @Override ++ protected Void evaluate() { ++ post(se); ++ return null; ++ } ++ }); ++ continueAfterAsyncAndSync.accept(se); ++ } else { ++ continueAfterAsync.accept(ae); ++ } ++ } else { ++ modernOnly.run(); ++ } ++ } ++ ++ private void processModern(final ChatComposer composer, final Set recipients, final Component message, final boolean cancelled) { ++ final AsyncChatEvent ae = this.createAsync(composer, recipients, message); ++ ae.setCancelled(cancelled); // propagate cancelled state ++ post(ae); ++ final boolean listenersOnSyncEvent = anyListeners(ChatEvent.getHandlerList()); ++ if (listenersOnSyncEvent) { ++ this.continueWithSyncFromWhereAsyncLeftOff(ae); ++ } else { ++ this.complete(ae); ++ } ++ } ++ ++ private void continueWithSyncFromWhereAsyncLeftOff(final AsyncChatEvent ae) { ++ this.queueIfAsyncOrRunImmediately(new Waitable() { ++ @Override ++ protected Void evaluate() { ++ final ChatEvent se = ChatProcessor.this.createSync(ae.composer(), ae.recipients(), ae.message()); ++ se.setCancelled(ae.isCancelled()); // propagate cancelled state ++ post(se); ++ ChatProcessor.this.complete(se); ++ return null; ++ } ++ }); ++ } ++ ++ private void complete(final AbstractChatEvent event) { ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ final CraftPlayer player = this.player.getBukkitEntity(); ++ ++ final Component message = event.composer().composeChat( ++ event.getPlayer(), ++ displayName(player), ++ event.message() ++ ); ++ ++ this.server.console.sendMessage(message); ++ ++ if (((LazyPlayerSet) event.recipients()).isLazy()) { ++ final IChatBaseComponent vanilla = PaperAdventure.asVanilla(message); ++ for (final EntityPlayer recipient : this.server.getPlayerList().players) { ++ recipient.sendMessage(vanilla, ChatMessageType.CHAT, this.player.getUniqueID()); ++ } ++ } else { ++ for (final Player recipient : event.recipients()) { ++ recipient.sendMessage(player, message, MessageType.CHAT); ++ } ++ } ++ } ++ ++ private AsyncChatEvent createAsync(final ChatComposer composer, final Set recipients, final Component message) { ++ return new AsyncChatEvent(this.async, this.player.getBukkitEntity(), recipients, composer, message); ++ } ++ ++ private ChatEvent createSync(final ChatComposer composer, final Set recipients, final Component message) { ++ return new ChatEvent(this.player.getBukkitEntity(), recipients, composer, message); ++ } ++ ++ private static String legacyDisplayName(final CraftPlayer player) { ++ return player.getDisplayName(); ++ } ++ ++ private static Component displayName(final CraftPlayer player) { ++ return player.displayName(); ++ } ++ ++ private static ChatComposer legacyComposer(final String format) { ++ return (player, displayName, message) -> PaperAdventure.LEGACY_SECTION_UXRC.deserialize(String.format(format, legacyDisplayName((CraftPlayer) player), PaperAdventure.LEGACY_SECTION_UXRC.serialize(message))).replaceText(URL_REPLACEMENT_CONFIG); ++ } ++ ++ private void queueIfAsyncOrRunImmediately(final Waitable waitable) { ++ if (this.async) { ++ this.server.processQueue.add(waitable); ++ } else { ++ waitable.run(); ++ } ++ try { ++ waitable.get(); ++ } catch (final InterruptedException e) { ++ Thread.currentThread().interrupt(); // tag, you're it ++ } catch (final ExecutionException e) { ++ throw new RuntimeException("Exception processing chat", e.getCause()); ++ } ++ } ++ ++ private static void post(final Event event) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ ++ private static boolean anyListeners(final HandlerList handlers) { ++ return handlers.getRegisteredListeners().length > 0; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/DisplayNames.java b/src/main/java/io/papermc/paper/adventure/DisplayNames.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b1d9d6276eb577ed3c66df1f89b3266d2c48eaf2 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/DisplayNames.java +@@ -0,0 +1,22 @@ ++package io.papermc.paper.adventure; ++ ++import net.minecraft.server.level.EntityPlayer; ++import org.bukkit.ChatColor; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++ ++public final class DisplayNames { ++ private DisplayNames() { ++ } ++ ++ public static String getLegacy(final CraftPlayer player) { ++ return getLegacy(player.getHandle()); ++ } ++ ++ public static String getLegacy(final EntityPlayer player) { ++ final String legacy = player.displayName; ++ if (legacy != null) { ++ return PaperAdventure.LEGACY_SECTION_UXRC.serialize(player.adventure$displayName) + ChatColor.getLastColors(player.displayName); ++ } ++ return PaperAdventure.LEGACY_SECTION_UXRC.serialize(player.adventure$displayName); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/NBTLegacyHoverEventSerializer.java b/src/main/java/io/papermc/paper/adventure/NBTLegacyHoverEventSerializer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..caa9708f321f04cd02534161231c05999bda4acd +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/NBTLegacyHoverEventSerializer.java +@@ -0,0 +1,88 @@ ++package io.papermc.paper.adventure; ++ ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import java.io.IOException; ++import java.util.UUID; ++import net.kyori.adventure.key.Key; ++import net.kyori.adventure.nbt.api.BinaryTagHolder; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.event.HoverEvent; ++import net.kyori.adventure.text.serializer.gson.LegacyHoverEventSerializer; ++import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; ++import net.kyori.adventure.util.Codec; ++import net.minecraft.nbt.MojangsonParser; ++import net.minecraft.nbt.NBTBase; ++import net.minecraft.nbt.NBTTagCompound; ++ ++final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer { ++ public static final NBTLegacyHoverEventSerializer INSTANCE = new NBTLegacyHoverEventSerializer(); ++ private static final Codec SNBT_CODEC = Codec.of(MojangsonParser::parse, NBTBase::toString); ++ ++ static final String ITEM_TYPE = "id"; ++ static final String ITEM_COUNT = "Count"; ++ static final String ITEM_TAG = "tag"; ++ ++ static final String ENTITY_NAME = "name"; ++ static final String ENTITY_TYPE = "type"; ++ static final String ENTITY_ID = "id"; ++ ++ NBTLegacyHoverEventSerializer() { ++ } ++ ++ @Override ++ public HoverEvent.ShowItem deserializeShowItem(final Component input) throws IOException { ++ final String raw = PlainComponentSerializer.plain().serialize(input); ++ try { ++ final NBTTagCompound contents = SNBT_CODEC.decode(raw); ++ final NBTTagCompound tag = contents.getCompound(ITEM_TAG); ++ return HoverEvent.ShowItem.of( ++ Key.key(contents.getString(ITEM_TYPE)), ++ contents.hasKey(ITEM_COUNT) ? contents.getByte(ITEM_COUNT) : 1, ++ tag.isEmpty() ? null : BinaryTagHolder.encode(tag, SNBT_CODEC) ++ ); ++ } catch (final CommandSyntaxException ex) { ++ throw new IOException(ex); ++ } ++ } ++ ++ @Override ++ public HoverEvent.ShowEntity deserializeShowEntity(final Component input, final Codec.Decoder componentCodec) throws IOException { ++ final String raw = PlainComponentSerializer.plain().serialize(input); ++ try { ++ final NBTTagCompound contents = SNBT_CODEC.decode(raw); ++ return HoverEvent.ShowEntity.of( ++ Key.key(contents.getString(ENTITY_TYPE)), ++ UUID.fromString(contents.getString(ENTITY_ID)), ++ componentCodec.decode(contents.getString(ENTITY_NAME)) ++ ); ++ } catch (final CommandSyntaxException ex) { ++ throw new IOException(ex); ++ } ++ } ++ ++ @Override ++ public Component serializeShowItem(final HoverEvent.ShowItem input) throws IOException { ++ final NBTTagCompound tag = new NBTTagCompound(); ++ tag.setString(ITEM_TYPE, input.item().asString()); ++ tag.setByte(ITEM_COUNT, (byte) input.count()); ++ if (input.nbt() != null) { ++ try { ++ tag.set(ITEM_TAG, input.nbt().get(SNBT_CODEC)); ++ } catch (final CommandSyntaxException ex) { ++ throw new IOException(ex); ++ } ++ } ++ return Component.text(SNBT_CODEC.encode(tag)); ++ } ++ ++ @Override ++ public Component serializeShowEntity(final HoverEvent.ShowEntity input, final Codec.Encoder componentCodec) throws IOException { ++ final NBTTagCompound tag = new NBTTagCompound(); ++ tag.setString(ENTITY_ID, input.id().toString()); ++ tag.setString(ENTITY_TYPE, input.type().asString()); ++ if (input.name() != null) { ++ tag.setString(ENTITY_NAME, componentCodec.encode(input.name())); ++ } ++ return Component.text(SNBT_CODEC.encode(tag)); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cd2da276c09dcf98c1c50dc66aa30dd3b67b43af +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java +@@ -0,0 +1,344 @@ ++package io.papermc.paper.adventure; ++ ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import io.netty.util.AttributeKey; ++import java.io.IOException; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Locale; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++import net.kyori.adventure.bossbar.BossBar; ++import net.kyori.adventure.inventory.Book; ++import net.kyori.adventure.key.Key; ++import net.kyori.adventure.nbt.api.BinaryTagHolder; ++import net.kyori.adventure.sound.Sound; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.TranslatableComponent; ++import net.kyori.adventure.text.flattener.ComponentFlattener; ++import net.kyori.adventure.text.format.TextColor; ++import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; ++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; ++import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; ++import net.kyori.adventure.translation.GlobalTranslator; ++import net.kyori.adventure.util.Codec; ++import net.minecraft.EnumChatFormat; ++import net.minecraft.locale.LocaleLanguage; ++import net.minecraft.nbt.MojangsonParser; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.nbt.NBTTagString; ++import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.world.BossBattle; ++import net.minecraft.world.item.ItemStack; ++import org.bukkit.ChatColor; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++public final class PaperAdventure { ++ public static final AttributeKey LOCALE_ATTRIBUTE = AttributeKey.valueOf("adventure:locale"); ++ private static final Pattern LOCALIZATION_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?s"); ++ public static final ComponentFlattener FLATTENER = ComponentFlattener.basic().toBuilder() ++ .complexMapper(TranslatableComponent.class, (translatable, consumer) -> { ++ final @NonNull String translated = LocaleLanguage.a().a(translatable.key()); ++ ++ final Matcher matcher = LOCALIZATION_PATTERN.matcher(translated); ++ final List args = translatable.args(); ++ int argPosition = 0; ++ int lastIdx = 0; ++ while (matcher.find()) { ++ // append prior ++ if (lastIdx < matcher.start()) { ++ consumer.accept(Component.text(translated.substring(lastIdx, matcher.start()))); ++ } ++ lastIdx = matcher.end(); ++ ++ final @Nullable String argIdx = matcher.group(1); ++ // calculate argument position ++ if (argIdx != null) { ++ try { ++ final int idx = Integer.parseInt(argIdx); ++ if (idx < args.size()) { ++ consumer.accept(args.get(idx)); ++ } ++ } catch (final NumberFormatException ex) { ++ // ignore, drop the format placeholder ++ } ++ } else { ++ final int idx = argPosition++; ++ if (idx < args.size()) { ++ consumer.accept(args.get(idx)); ++ } ++ } ++ } ++ ++ // append tail ++ if (lastIdx < translated.length()) { ++ consumer.accept(Component.text(translated.substring(lastIdx))); ++ } ++ }) ++ .build(); ++ public static final LegacyComponentSerializer LEGACY_SECTION_UXRC = LegacyComponentSerializer.builder().flattener(FLATTENER).hexColors().useUnusualXRepeatedCharacterHexFormat().build(); ++ public static final PlainComponentSerializer PLAIN = PlainComponentSerializer.builder().flattener(FLATTENER).build(); ++ public static final GsonComponentSerializer GSON = GsonComponentSerializer.builder() ++ .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.INSTANCE) ++ .build(); ++ public static final GsonComponentSerializer COLOR_DOWNSAMPLING_GSON = GsonComponentSerializer.builder() ++ .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.INSTANCE) ++ .downsampleColors() ++ .build(); ++ private static final Codec NBT_CODEC = new Codec() { ++ @Override ++ public @NonNull NBTTagCompound decode(final @NonNull String encoded) throws IOException { ++ try { ++ return MojangsonParser.parse(encoded); ++ } catch (final CommandSyntaxException e) { ++ throw new IOException(e); ++ } ++ } ++ ++ @Override ++ public @NonNull String encode(final @NonNull NBTTagCompound decoded) { ++ return decoded.toString(); ++ } ++ }; ++ static final WrapperAwareSerializer WRAPPER_AWARE_SERIALIZER = new WrapperAwareSerializer(); ++ ++ private PaperAdventure() { ++ } ++ ++ // Key ++ ++ public static MinecraftKey asVanilla(final Key key) { ++ return new MinecraftKey(key.namespace(), key.value()); ++ } ++ ++ public static MinecraftKey asVanillaNullable(final Key key) { ++ if (key == null) { ++ return null; ++ } ++ return new MinecraftKey(key.namespace(), key.value()); ++ } ++ ++ // Component ++ ++ public static Component asAdventure(final IChatBaseComponent component) { ++ return GSON.serializer().fromJson(IChatBaseComponent.ChatSerializer.toJsonTree(component), Component.class); ++ } ++ ++ public static ArrayList asAdventure(final List vanillas) { ++ final ArrayList adventures = new ArrayList<>(vanillas.size()); ++ for (final IChatBaseComponent vanilla : vanillas) { ++ adventures.add(asAdventure(vanilla)); ++ } ++ return adventures; ++ } ++ ++ public static ArrayList asAdventureFromJson(final List jsonStrings) { ++ final ArrayList adventures = new ArrayList<>(jsonStrings.size()); ++ for (final String json : jsonStrings) { ++ adventures.add(GsonComponentSerializer.gson().deserialize(json)); ++ } ++ return adventures; ++ } ++ ++ public static List asJson(final List adventures) { ++ final List jsons = new ArrayList<>(adventures.size()); ++ for (final Component component : adventures) { ++ jsons.add(GsonComponentSerializer.gson().serialize(component)); ++ } ++ return jsons; ++ } ++ ++ public static IChatBaseComponent asVanilla(final Component component) { ++ if (true) return new AdventureComponent(component); ++ return IChatBaseComponent.ChatSerializer.fromJsonTree(GSON.serializer().toJsonTree(component)); ++ } ++ ++ public static List asVanilla(final List adventures) { ++ final List vanillas = new ArrayList<>(adventures.size()); ++ for (final Component adventure : adventures) { ++ vanillas.add(asVanilla(adventure)); ++ } ++ return vanillas; ++ } ++ ++ public static String asJsonString(final Component component, final Locale locale) { ++ return GSON.serialize( ++ GlobalTranslator.render( ++ component, ++ // play it safe ++ locale != null ++ ? locale ++ : Locale.US ++ ) ++ ); ++ } ++ ++ public static String asJsonString(final IChatBaseComponent component, final Locale locale) { ++ if (component instanceof AdventureComponent) { ++ return asJsonString(((AdventureComponent) component).wrapped, locale); ++ } ++ return IChatBaseComponent.ChatSerializer.componentToJson(component); ++ } ++ ++ // thank you for being worse than wet socks, Bukkit ++ public static String superHackyLegacyRepresentationOfComponent(final Component component, final String string) { ++ return LEGACY_SECTION_UXRC.serialize(component) + ChatColor.getLastColors(string); ++ } ++ ++ // BossBar ++ ++ public static BossBattle.BarColor asVanilla(final BossBar.Color color) { ++ if (color == BossBar.Color.PINK) { ++ return BossBattle.BarColor.PINK; ++ } else if (color == BossBar.Color.BLUE) { ++ return BossBattle.BarColor.BLUE; ++ } else if (color == BossBar.Color.RED) { ++ return BossBattle.BarColor.RED; ++ } else if (color == BossBar.Color.GREEN) { ++ return BossBattle.BarColor.GREEN; ++ } else if (color == BossBar.Color.YELLOW) { ++ return BossBattle.BarColor.YELLOW; ++ } else if (color == BossBar.Color.PURPLE) { ++ return BossBattle.BarColor.PURPLE; ++ } else if (color == BossBar.Color.WHITE) { ++ return BossBattle.BarColor.WHITE; ++ } ++ throw new IllegalArgumentException(color.name()); ++ } ++ ++ public static BossBar.Color asAdventure(final BossBattle.BarColor color) { ++ if(color == BossBattle.BarColor.PINK) { ++ return BossBar.Color.PINK; ++ } else if(color == BossBattle.BarColor.BLUE) { ++ return BossBar.Color.BLUE; ++ } else if(color == BossBattle.BarColor.RED) { ++ return BossBar.Color.RED; ++ } else if(color == BossBattle.BarColor.GREEN) { ++ return BossBar.Color.GREEN; ++ } else if(color == BossBattle.BarColor.YELLOW) { ++ return BossBar.Color.YELLOW; ++ } else if(color == BossBattle.BarColor.PURPLE) { ++ return BossBar.Color.PURPLE; ++ } else if(color == BossBattle.BarColor.WHITE) { ++ return BossBar.Color.WHITE; ++ } ++ throw new IllegalArgumentException(color.name()); ++ } ++ ++ public static BossBattle.BarStyle asVanilla(final BossBar.Overlay overlay) { ++ if (overlay == BossBar.Overlay.PROGRESS) { ++ return BossBattle.BarStyle.PROGRESS; ++ } else if (overlay == BossBar.Overlay.NOTCHED_6) { ++ return BossBattle.BarStyle.NOTCHED_6; ++ } else if (overlay == BossBar.Overlay.NOTCHED_10) { ++ return BossBattle.BarStyle.NOTCHED_10; ++ } else if (overlay == BossBar.Overlay.NOTCHED_12) { ++ return BossBattle.BarStyle.NOTCHED_12; ++ } else if (overlay == BossBar.Overlay.NOTCHED_20) { ++ return BossBattle.BarStyle.NOTCHED_20; ++ } ++ throw new IllegalArgumentException(overlay.name()); ++ } ++ ++ public static BossBar.Overlay asAdventure(final BossBattle.BarStyle overlay) { ++ if (overlay == BossBattle.BarStyle.PROGRESS) { ++ return BossBar.Overlay.PROGRESS; ++ } else if (overlay == BossBattle.BarStyle.NOTCHED_6) { ++ return BossBar.Overlay.NOTCHED_6; ++ } else if (overlay == BossBattle.BarStyle.NOTCHED_10) { ++ return BossBar.Overlay.NOTCHED_10; ++ } else if (overlay == BossBattle.BarStyle.NOTCHED_12) { ++ return BossBar.Overlay.NOTCHED_12; ++ } else if (overlay == BossBattle.BarStyle.NOTCHED_20) { ++ return BossBar.Overlay.NOTCHED_20; ++ } ++ throw new IllegalArgumentException(overlay.name()); ++ } ++ ++ public static void setFlag(final BossBar bar, final BossBar.Flag flag, final boolean value) { ++ if (value) { ++ bar.addFlag(flag); ++ } else { ++ bar.removeFlag(flag); ++ } ++ } ++ ++ // Book ++ ++ public static ItemStack asItemStack(final Book book, final Locale locale) { ++ final ItemStack item = new ItemStack(net.minecraft.world.item.Items.WRITTEN_BOOK, 1); ++ final NBTTagCompound tag = item.getOrCreateTag(); ++ tag.setString("title", asJsonString(book.title(), locale)); ++ tag.setString("author", asJsonString(book.author(), locale)); ++ final NBTTagList pages = new NBTTagList(); ++ for (final Component page : book.pages()) { ++ pages.add(NBTTagString.create(asJsonString(page, locale))); ++ } ++ tag.set("pages", pages); ++ return item; ++ } ++ ++ // Sounds ++ ++ public static SoundCategory asVanilla(final Sound.Source source) { ++ if (source == Sound.Source.MASTER) { ++ return SoundCategory.MASTER; ++ } else if (source == Sound.Source.MUSIC) { ++ return SoundCategory.MUSIC; ++ } else if (source == Sound.Source.RECORD) { ++ return SoundCategory.RECORDS; ++ } else if (source == Sound.Source.WEATHER) { ++ return SoundCategory.WEATHER; ++ } else if (source == Sound.Source.BLOCK) { ++ return SoundCategory.BLOCKS; ++ } else if (source == Sound.Source.HOSTILE) { ++ return SoundCategory.HOSTILE; ++ } else if (source == Sound.Source.NEUTRAL) { ++ return SoundCategory.NEUTRAL; ++ } else if (source == Sound.Source.PLAYER) { ++ return SoundCategory.PLAYERS; ++ } else if (source == Sound.Source.AMBIENT) { ++ return SoundCategory.AMBIENT; ++ } else if (source == Sound.Source.VOICE) { ++ return SoundCategory.VOICE; ++ } ++ throw new IllegalArgumentException(source.name()); ++ } ++ ++ public static @Nullable SoundCategory asVanillaNullable(final Sound.@Nullable Source source) { ++ if (source == null) { ++ return null; ++ } ++ return asVanilla(source); ++ } ++ ++ // NBT ++ ++ public static @Nullable BinaryTagHolder asBinaryTagHolder(final @Nullable NBTTagCompound tag) { ++ if (tag == null) { ++ return null; ++ } ++ try { ++ return BinaryTagHolder.encode(tag, NBT_CODEC); ++ } catch (final IOException e) { ++ return null; ++ } ++ } ++ ++ // Colors ++ ++ public static @NonNull TextColor asAdventure(EnumChatFormat minecraftColor) { ++ if (minecraftColor.e() == null) { ++ throw new IllegalArgumentException("Not a valid color"); ++ } ++ return TextColor.color(minecraftColor.e()); ++ } ++ ++ public static @Nullable EnumChatFormat asVanilla(TextColor color) { ++ return EnumChatFormat.getByHexValue(color.value()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/VanillaBossBarListener.java b/src/main/java/io/papermc/paper/adventure/VanillaBossBarListener.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3a4158781e464d9a860bab72ed719a41929c8add +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/VanillaBossBarListener.java +@@ -0,0 +1,41 @@ ++package io.papermc.paper.adventure; ++ ++import java.util.Set; ++import java.util.function.Consumer; ++import net.kyori.adventure.bossbar.BossBar; ++import net.kyori.adventure.text.Component; ++import net.minecraft.network.protocol.game.PacketPlayOutBoss; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++public final class VanillaBossBarListener implements BossBar.Listener { ++ private final Consumer action; ++ ++ public VanillaBossBarListener(final Consumer action) { ++ this.action = action; ++ } ++ ++ @Override ++ public void bossBarNameChanged(final @NonNull BossBar bar, final @NonNull Component oldName, final @NonNull Component newName) { ++ this.action.accept(PacketPlayOutBoss.Action.UPDATE_NAME); ++ } ++ ++ @Override ++ public void bossBarProgressChanged(final @NonNull BossBar bar, final float oldProgress, final float newProgress) { ++ this.action.accept(PacketPlayOutBoss.Action.UPDATE_PCT); ++ } ++ ++ @Override ++ public void bossBarColorChanged(final @NonNull BossBar bar, final BossBar.@NonNull Color oldColor, final BossBar.@NonNull Color newColor) { ++ this.action.accept(PacketPlayOutBoss.Action.UPDATE_STYLE); ++ } ++ ++ @Override ++ public void bossBarOverlayChanged(final @NonNull BossBar bar, final BossBar.@NonNull Overlay oldOverlay, final BossBar.@NonNull Overlay newOverlay) { ++ this.action.accept(PacketPlayOutBoss.Action.UPDATE_STYLE); ++ } ++ ++ @Override ++ public void bossBarFlagsChanged(final @NonNull BossBar bar, final @NonNull Set flagsAdded, final @NonNull Set flagsRemoved) { ++ this.action.accept(PacketPlayOutBoss.Action.UPDATE_PROPERTIES); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java b/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..625b57a4274d6348a85897b92ff07fee7ae3a7ab +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java +@@ -0,0 +1,20 @@ ++package io.papermc.paper.adventure; ++ ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.serializer.ComponentSerializer; ++import net.minecraft.network.chat.IChatBaseComponent; ++ ++final class WrapperAwareSerializer implements ComponentSerializer { ++ @Override ++ public Component deserialize(final IChatBaseComponent input) { ++ if (input instanceof AdventureComponent) { ++ return ((AdventureComponent) input).wrapped; ++ } ++ return PaperAdventure.GSON.serializer().fromJson(IChatBaseComponent.ChatSerializer.toJsonTree(input), Component.class); ++ } ++ ++ @Override ++ public IChatBaseComponent serialize(final Component component) { ++ return IChatBaseComponent.ChatSerializer.fromJsonTree(PaperAdventure.GSON.serializer().toJsonTree(component)); ++ } ++} +diff --git a/src/main/java/net/kyori/adventure/bossbar/HackyBossBarPlatformBridge.java b/src/main/java/net/kyori/adventure/bossbar/HackyBossBarPlatformBridge.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b3e26b9ecca7055760d05975d36c9ae74e0d7d4b +--- /dev/null ++++ b/src/main/java/net/kyori/adventure/bossbar/HackyBossBarPlatformBridge.java +@@ -0,0 +1,36 @@ ++package net.kyori.adventure.bossbar; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.adventure.VanillaBossBarListener; ++import net.minecraft.server.level.BossBattleServer; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++ ++public abstract class HackyBossBarPlatformBridge { ++ public BossBattleServer vanilla$bar; ++ private VanillaBossBarListener vanilla$listener; ++ ++ public final void paper$playerShow(final CraftPlayer player) { ++ if (this.vanilla$bar == null) { ++ final BossBar $this = (BossBar) this; ++ this.vanilla$bar = new BossBattleServer( ++ PaperAdventure.asVanilla($this.name()), ++ PaperAdventure.asVanilla($this.color()), ++ PaperAdventure.asVanilla($this.overlay()) ++ ); ++ this.vanilla$bar.adventure = $this; ++ this.vanilla$listener = new VanillaBossBarListener(this.vanilla$bar::sendUpdate); ++ $this.addListener(this.vanilla$listener); ++ } ++ this.vanilla$bar.addPlayer(player.getHandle()); ++ } ++ ++ public final void paper$playerHide(final CraftPlayer player) { ++ if (this.vanilla$bar != null) { ++ this.vanilla$bar.removePlayer(player.getHandle()); ++ if (this.vanilla$bar.getPlayers().isEmpty()) { ++ ((BossBar) this).removeListener(this.vanilla$listener); ++ this.vanilla$bar = null; ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/EnumChatFormat.java b/src/main/java/net/minecraft/EnumChatFormat.java +index 75e38f05c10713f773a8763100dfc0777521dba6..cd93f99e939438c572a4258d299a6038ebfc60a8 100644 +--- a/src/main/java/net/minecraft/EnumChatFormat.java ++++ b/src/main/java/net/minecraft/EnumChatFormat.java +@@ -61,6 +61,7 @@ public enum EnumChatFormat { + return !this.A && this != EnumChatFormat.RESET; + } + ++ @Nullable public Integer getHexValue() { return this.e(); } // Paper - OBFHELPER + @Nullable + public Integer e() { + return this.D; +@@ -84,6 +85,18 @@ public enum EnumChatFormat { + return s == null ? null : (EnumChatFormat) EnumChatFormat.w.get(c(s)); + } + ++ // Paper start ++ @Nullable public static EnumChatFormat getByHexValue(int i) { ++ for (EnumChatFormat value : values()) { ++ if (value.getHexValue() != null && value.getHexValue() == i) { ++ return value; ++ } ++ } ++ ++ return null; ++ } ++ // Paper end ++ + @Nullable + public static EnumChatFormat a(int i) { + if (i < 0) { +diff --git a/src/main/java/net/minecraft/nbt/NBTTagString.java b/src/main/java/net/minecraft/nbt/NBTTagString.java +index e26ef49d9dde8ed0fb4267b48cb597563967f313..0e41fdb6ba711fbd2240d62e2030b3a12e14c8d6 100644 +--- a/src/main/java/net/minecraft/nbt/NBTTagString.java ++++ b/src/main/java/net/minecraft/nbt/NBTTagString.java +@@ -43,6 +43,7 @@ public class NBTTagString implements NBTBase { + this.data = s; + } + ++ public static NBTTagString create(final String value) { return a(value); } // Paper - OBFHELPER + public static NBTTagString a(String s) { + return s.isEmpty() ? NBTTagString.b : new NBTTagString(s); + } +diff --git a/src/main/java/net/minecraft/network/PacketDataSerializer.java b/src/main/java/net/minecraft/network/PacketDataSerializer.java +index 5413bf93f7f0f4491fca1f07c47a925fdace7751..5f1c5dd7902f6cff5acae05e8c6bf58a1ba5bdf1 100644 +--- a/src/main/java/net/minecraft/network/PacketDataSerializer.java ++++ b/src/main/java/net/minecraft/network/PacketDataSerializer.java +@@ -10,6 +10,7 @@ import io.netty.buffer.ByteBufOutputStream; + import io.netty.handler.codec.DecoderException; + import io.netty.handler.codec.EncoderException; + import io.netty.util.ByteProcessor; ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import java.io.DataInput; + import java.io.DataOutput; + import java.io.IOException; +@@ -44,6 +45,7 @@ import org.bukkit.craftbukkit.inventory.CraftItemStack; // CraftBukkit + public class PacketDataSerializer extends ByteBuf { + + private final ByteBuf a; ++ public java.util.Locale adventure$locale; // Paper + + public PacketDataSerializer(ByteBuf bytebuf) { + this.a = bytebuf; +@@ -165,8 +167,15 @@ public class PacketDataSerializer extends ByteBuf { + return IChatBaseComponent.ChatSerializer.a(this.e(262144)); + } + ++ // Paper start ++ public PacketDataSerializer writeComponent(final net.kyori.adventure.text.Component component) { ++ return this.writeUtf(PaperAdventure.asJsonString(component, this.adventure$locale), 262144); ++ } ++ // Paper end ++ + public PacketDataSerializer a(IChatBaseComponent ichatbasecomponent) { +- return this.a(IChatBaseComponent.ChatSerializer.a(ichatbasecomponent), 262144); ++ //return this.a(IChatBaseComponent.ChatSerializer.a(ichatbasecomponent), 262144); // Paper - comment ++ return this.writeUtf(PaperAdventure.asJsonString(ichatbasecomponent, this.adventure$locale), 262144); // Paper + } + + public > T a(Class oclass) { +@@ -349,6 +358,7 @@ public class PacketDataSerializer extends ByteBuf { + return this.a(s, 32767); + } + ++ public PacketDataSerializer writeUtf(final String string, final int maxLength) { return this.a(string, maxLength); } // Paper - OBFHELPER + public PacketDataSerializer a(String s, int i) { + byte[] abyte = s.getBytes(StandardCharsets.UTF_8); + +diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java +index dc8cc8d6c00176c8562086282f726dc1b24b2c65..2f6da89d6b25ba5144ec15b1bf0e8ed13278e85e 100644 +--- a/src/main/java/net/minecraft/network/PacketEncoder.java ++++ b/src/main/java/net/minecraft/network/PacketEncoder.java +@@ -3,6 +3,7 @@ package net.minecraft.network; + import io.netty.buffer.ByteBuf; + import io.netty.channel.ChannelHandlerContext; + import io.netty.handler.codec.MessageToByteEncoder; ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import java.io.IOException; + import net.minecraft.network.protocol.EnumProtocolDirection; + import net.minecraft.network.protocol.Packet; +@@ -37,6 +38,7 @@ public class PacketEncoder extends MessageToByteEncoder> { + throw new IOException("Can't serialize unregistered packet"); + } else { + PacketDataSerializer packetdataserializer = new PacketDataSerializer(bytebuf); ++ packetdataserializer.adventure$locale = channelhandlercontext.channel().attr(PaperAdventure.LOCALE_ATTRIBUTE).get(); // Paper + + packetdataserializer.d(integer); + +diff --git a/src/main/java/net/minecraft/network/chat/IChatBaseComponent.java b/src/main/java/net/minecraft/network/chat/IChatBaseComponent.java +index 85140d961722e86abfe7006a0ad752751e73c721..c7c191b0a9889450fdf495f5aa45d59f159f1401 100644 +--- a/src/main/java/net/minecraft/network/chat/IChatBaseComponent.java ++++ b/src/main/java/net/minecraft/network/chat/IChatBaseComponent.java +@@ -1,5 +1,6 @@ + package net.minecraft.network.chat; + ++import io.papermc.paper.adventure.AdventureComponent; // Paper + import com.google.gson.Gson; + import com.google.gson.GsonBuilder; + import com.google.gson.JsonArray; +@@ -111,6 +112,7 @@ public interface IChatBaseComponent extends Message, IChatFormatted, Iterable { + + private IChatBaseComponent a; ++ public net.kyori.adventure.text.Component adventure$message; // Paper + public net.md_5.bungee.api.chat.BaseComponent[] components; // Spigot + private ChatMessageType b; + private UUID c; +@@ -32,6 +33,11 @@ public class PacketPlayOutChat implements Packet { + + @Override + public void b(PacketDataSerializer packetdataserializer) throws IOException { ++ // Paper start ++ if (this.adventure$message != null) { ++ packetdataserializer.writeComponent(this.adventure$message); ++ } else ++ // Paper end + // Spigot start + if (components != null) { + packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(components)); +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutPlayerListHeaderFooter.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutPlayerListHeaderFooter.java +index 0268b8e6595ee919bcd55a74ba872a2b7d2a17d8..4ff73a4fc5e8a8739e57d2bac65076812cbe5132 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutPlayerListHeaderFooter.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutPlayerListHeaderFooter.java +@@ -9,6 +9,10 @@ public class PacketPlayOutPlayerListHeaderFooter implements Packet { + + private PacketPlayOutTitle.EnumTitleAction a; + private IChatBaseComponent b; ++ public net.kyori.adventure.text.Component adventure$text; // Paper + private int c; + private int d; + private int e; +@@ -51,6 +52,11 @@ public class PacketPlayOutTitle implements Packet { + public void b(PacketDataSerializer packetdataserializer) throws IOException { + packetdataserializer.a((Enum) this.a); + if (this.a == PacketPlayOutTitle.EnumTitleAction.TITLE || this.a == PacketPlayOutTitle.EnumTitleAction.SUBTITLE || this.a == PacketPlayOutTitle.EnumTitleAction.ACTIONBAR) { ++ // Paper start ++ if (this.adventure$text != null) { ++ packetdataserializer.writeComponent(this.adventure$text); ++ } else ++ // Paper end + packetdataserializer.a(this.b); + } + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index e818bf022b74cae34a512d8c98b47ec3e5c74b9a..bb309790dc90aedabb3c48ea21cd87d1819d2261 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -143,6 +143,7 @@ import net.minecraft.world.item.enchantment.EnchantmentManager; + import net.minecraft.world.level.block.BlockChest; + import net.minecraft.world.level.dimension.DimensionManager; + import net.minecraft.world.scores.Scoreboard; ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import org.bukkit.Bukkit; + import org.bukkit.GameMode; + import org.bukkit.Location; +@@ -211,6 +212,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + // CraftBukkit start + public String displayName; ++ public net.kyori.adventure.text.Component adventure$displayName; // Paper + public IChatBaseComponent listName; + public org.bukkit.Location compassTarget; + public int newExp = 0; +@@ -241,6 +243,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + // CraftBukkit start + this.displayName = this.getName(); ++ this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getName()); // Paper + this.canPickUpLoot = true; + this.maxHealthCache = this.getMaxHealth(); + } +@@ -694,23 +697,17 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + IChatBaseComponent defaultMessage = this.getCombatTracker().getDeathMessage(); + +- String deathmessage = defaultMessage.getString(); +- org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, deathmessage, keepInventory); ++ org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, PaperAdventure.asAdventure(defaultMessage), defaultMessage.getString(), keepInventory); // Paper - Adventure + + // SPIGOT-943 - only call if they have an inventory open + if (this.activeContainer != this.defaultContainer) { + this.closeInventory(); + } + +- String deathMessage = event.getDeathMessage(); ++ net.kyori.adventure.text.Component deathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure + +- if (deathMessage != null && deathMessage.length() > 0 && flag) { // TODO: allow plugins to override? +- IChatBaseComponent ichatbasecomponent; +- if (deathMessage.equals(deathmessage)) { +- ichatbasecomponent = this.getCombatTracker().getDeathMessage(); +- } else { +- ichatbasecomponent = org.bukkit.craftbukkit.util.CraftChatMessage.fromStringOrNull(deathMessage); +- } ++ if (deathMessage != null && deathMessage != net.kyori.adventure.text.Component.empty() && flag) { // Paper - Adventure // TODO: allow plugins to override? ++ IChatBaseComponent ichatbasecomponent = PaperAdventure.asVanilla(deathMessage); // Paper - Adventure + + this.playerConnection.a((Packet) (new PacketPlayOutCombatEvent(this.getCombatTracker(), PacketPlayOutCombatEvent.EnumCombatEventType.ENTITY_DIED, ichatbasecomponent)), (future) -> { + if (!future.isSuccess()) { +@@ -1660,6 +1657,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.a(ichatbasecomponent, ChatMessageType.SYSTEM, uuid); + } + ++ public void sendMessage(final IChatBaseComponent message, final ChatMessageType type, final UUID sender) { this.a(message, type, sender); } // Paper - OBFHELPER + public void a(IChatBaseComponent ichatbasecomponent, ChatMessageType chatmessagetype, UUID uuid) { + this.playerConnection.a((Packet) (new PacketPlayOutChat(ichatbasecomponent, chatmessagetype, uuid)), (future) -> { + if (!future.isSuccess() && (chatmessagetype == ChatMessageType.GAME_INFO || chatmessagetype == ChatMessageType.SYSTEM)) { +@@ -1682,6 +1680,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + + public String locale = "en_us"; // CraftBukkit - add, lowercase ++ public java.util.Locale adventure$locale = java.util.Locale.US; // Paper + public void a(PacketPlayInSettings packetplayinsettings) { + // CraftBukkit start + if (getMainHand() != packetplayinsettings.getMainHand()) { +@@ -1693,6 +1692,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.server.server.getPluginManager().callEvent(event); + } + this.locale = packetplayinsettings.locale; ++ // Paper start ++ this.adventure$locale = net.kyori.adventure.translation.Translator.parseLocale(this.locale); ++ this.playerConnection.networkManager.channel.attr(PaperAdventure.LOCALE_ATTRIBUTE).set(this.adventure$locale); ++ // Paper end + this.clientViewDistance = packetplayinsettings.viewDistance; + // CraftBukkit end + this.bY = packetplayinsettings.d(); +diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java +index 49f6b20ceee1b58e66e72d161d20226fa17850fd..c4e3dd34c20c97ae1397cf51125f51a5b77d9437 100644 +--- a/src/main/java/net/minecraft/server/network/LoginListener.java ++++ b/src/main/java/net/minecraft/server/network/LoginListener.java +@@ -37,6 +37,7 @@ import org.apache.logging.log4j.Logger; + + // CraftBukkit start + import net.minecraft.network.chat.ChatComponentText; ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import org.bukkit.craftbukkit.util.Waitable; + import org.bukkit.event.player.AsyncPlayerPreLoginEvent; + import org.bukkit.event.player.PlayerPreLoginEvent; +@@ -300,7 +301,7 @@ public class LoginListener implements PacketLoginInListener { + if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) { + final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId); + if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) { +- event.disallow(asyncEvent.getResult(), asyncEvent.getKickMessage()); ++ event.disallow(asyncEvent.getResult(), asyncEvent.kickMessage()); // Paper - Adventure + } + Waitable waitable = new Waitable() { + @Override +@@ -311,12 +312,12 @@ public class LoginListener implements PacketLoginInListener { + + LoginListener.this.server.processQueue.add(waitable); + if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) { +- disconnect(event.getKickMessage()); ++ disconnect(PaperAdventure.asVanilla(event.kickMessage())); // Paper - Adventure + return; + } + } else { + if (asyncEvent.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { +- disconnect(asyncEvent.getKickMessage()); ++ disconnect(PaperAdventure.asVanilla(asyncEvent.kickMessage())); // Paper - Adventure + return; + } + } +diff --git a/src/main/java/net/minecraft/server/network/PacketStatusListener.java b/src/main/java/net/minecraft/server/network/PacketStatusListener.java +index 2a96564c1656d42a74c331a6178e511cd5347a66..d219eda271a71f786808a6958b829fca40a1aaba 100644 +--- a/src/main/java/net/minecraft/server/network/PacketStatusListener.java ++++ b/src/main/java/net/minecraft/server/network/PacketStatusListener.java +@@ -56,7 +56,7 @@ public class PacketStatusListener implements PacketStatusInListener { + CraftIconCache icon = minecraftServer.server.getServerIcon(); + + ServerListPingEvent() { +- super(((InetSocketAddress) networkManager.getSocketAddress()).getAddress(), minecraftServer.getMotd(), minecraftServer.getPlayerList().getMaxPlayers()); ++ super(((InetSocketAddress) networkManager.getSocketAddress()).getAddress(), minecraftServer.server.motd(), minecraftServer.getPlayerList().getMaxPlayers()); // Paper - Adventure + } + + @Override +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 4ed497ee04d9e9116e1f7d90bf975aeadd24aa93..a39f58e0c60b5e3ccc3b725f1f4167d52b230e11 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -159,6 +159,8 @@ import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + + // CraftBukkit start ++import io.papermc.paper.adventure.ChatProcessor; // Paper ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import java.util.concurrent.ExecutionException; + import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + import net.minecraft.network.protocol.game.PacketPlayOutAttachEntity; +@@ -390,21 +392,24 @@ public class PlayerConnection implements PacketListenerPlayIn { + return this.minecraftServer.a(this.player.getProfile()); + } + +- // CraftBukkit start +- @Deprecated +- public void disconnect(IChatBaseComponent ichatbasecomponent) { +- disconnect(CraftChatMessage.fromComponent(ichatbasecomponent)); ++ public void disconnect(String s) { ++ // Paper start ++ this.disconnect(PaperAdventure.LEGACY_SECTION_UXRC.deserialize(s)); + } +- // CraftBukkit end + +- public void disconnect(String s) { ++ public void disconnect(final IChatBaseComponent reason) { ++ this.disconnect(PaperAdventure.asAdventure(reason)); ++ } ++ ++ public void disconnect(net.kyori.adventure.text.Component reason) { ++ // Paper end + // CraftBukkit start - fire PlayerKickEvent + if (this.processedDisconnect) { + return; + } +- String leaveMessage = EnumChatFormat.YELLOW + this.player.getName() + " left the game."; ++ net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, this.player.getBukkitEntity().displayName()); // Paper - Adventure + +- PlayerKickEvent event = new PlayerKickEvent(this.server.getPlayer(this.player), s, leaveMessage); ++ PlayerKickEvent event = new PlayerKickEvent(this.server.getPlayer(this.player), reason, leaveMessage); // Paper - Adventure + + if (this.server.getServer().isRunning()) { + this.server.getPluginManager().callEvent(event); +@@ -415,8 +420,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + return; + } + // Send the possibly modified leave message +- s = event.getReason(); +- final IChatBaseComponent ichatbasecomponent = CraftChatMessage.fromString(s, true)[0]; ++ final IChatBaseComponent ichatbasecomponent = PaperAdventure.asVanilla(event.reason()); // Paper - Adventure + // CraftBukkit end + + this.networkManager.sendPacket(new PacketPlayOutKickDisconnect(ichatbasecomponent), (future) -> { +@@ -1632,9 +1636,11 @@ public class PlayerConnection implements PacketListenerPlayIn { + */ + + this.player.p(); +- String quitMessage = this.minecraftServer.getPlayerList().disconnect(this.player); +- if ((quitMessage != null) && (quitMessage.length() > 0)) { +- this.minecraftServer.getPlayerList().sendMessage(CraftChatMessage.fromString(quitMessage)); ++ // Paper start - Adventure ++ net.kyori.adventure.text.Component quitMessage = this.minecraftServer.getPlayerList().disconnect(this.player); ++ if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { ++ this.minecraftServer.getPlayerList().sendMessage(PaperAdventure.asVanilla(quitMessage)); ++ // Paper end + } + // CraftBukkit end + ITextFilter itextfilter = this.player.Q(); +@@ -1850,8 +1856,13 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.handleCommand(s); + } else if (this.player.getChatFlags() == EnumChatVisibility.SYSTEM) { + // Do nothing, this is coming from a plugin +- } else { +- Player player = this.getPlayer(); ++ // Paper start ++ } else if (true) { ++ final ChatProcessor cp = new ChatProcessor(this.minecraftServer, this.player, s, async); ++ cp.process(); ++ // Paper end ++ } else if (false) { // Paper ++ Player player = this.getPlayer(); // Paper + AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet(minecraftServer)); + this.server.getPluginManager().callEvent(event); + +@@ -2669,21 +2680,20 @@ public class PlayerConnection implements PacketListenerPlayIn { + return; + } + +- // CraftBukkit start +- Player player = this.server.getPlayer(this.player); +- int x = packetplayinupdatesign.b().getX(); +- int y = packetplayinupdatesign.b().getY(); +- int z = packetplayinupdatesign.b().getZ(); +- String[] lines = new String[4]; ++ // CraftBukkit start // Paper start - Adventure ++ List lines = new java.util.ArrayList<>(); + + for (int i = 0; i < list.size(); ++i) { +- lines[i] = EnumChatFormat.a(new ChatComponentText(EnumChatFormat.a((String) list.get(i))).getString()); ++ lines.add(net.kyori.adventure.text.Component.text(list.get(i))); + } +- SignChangeEvent event = new SignChangeEvent((org.bukkit.craftbukkit.block.CraftBlock) player.getWorld().getBlockAt(x, y, z), this.server.getPlayer(this.player), lines); ++ SignChangeEvent event = new SignChangeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(worldserver, blockposition), this.getPlayer(), lines); + this.server.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +- System.arraycopy(org.bukkit.craftbukkit.block.CraftSign.sanitizeLines(event.getLines()), 0, tileentitysign.lines, 0, 4); ++ for (int i = 0; i < 4; i++) { ++ tileentitysign.a(i, PaperAdventure.asVanilla(event.line(i))); ++ } ++ // Paper end + tileentitysign.isEditable = false; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index c601a5c577e438a3fa8dd4c5f36dbe9494b03d52..15418868c2b92498139e66d913ee1c35b3abf0cf 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -8,6 +8,7 @@ import com.mojang.authlib.GameProfile; + import com.mojang.serialization.DataResult; + import com.mojang.serialization.Dynamic; + import io.netty.buffer.Unpooled; ++import io.papermc.paper.adventure.PaperAdventure; + import java.io.File; + import java.net.SocketAddress; + import java.text.SimpleDateFormat; +@@ -92,6 +93,7 @@ import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + + // CraftBukkit start ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import com.google.common.base.Predicate; + import com.google.common.collect.Iterables; + +@@ -255,7 +257,7 @@ public abstract class PlayerList { + } + // CraftBukkit start + chatmessage.a(EnumChatFormat.YELLOW); +- String joinMessage = CraftChatMessage.fromComponent(chatmessage); ++ IChatBaseComponent joinMessage = chatmessage; // Paper - Adventure + + playerconnection.a(entityplayer.locX(), entityplayer.locY(), entityplayer.locZ(), entityplayer.yaw, entityplayer.pitch); + this.players.add(entityplayer); +@@ -264,19 +266,18 @@ public abstract class PlayerList { + // this.sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, new EntityPlayer[]{entityplayer})); // CraftBukkit - replaced with loop below + + // CraftBukkit start +- PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(cserver.getPlayer(entityplayer), joinMessage); ++ PlayerJoinEvent playerJoinEvent = new org.bukkit.event.player.PlayerJoinEvent(cserver.getPlayer(entityplayer), PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure + cserver.getPluginManager().callEvent(playerJoinEvent); + + if (!entityplayer.playerConnection.networkManager.isConnected()) { + return; + } + +- joinMessage = playerJoinEvent.getJoinMessage(); ++ final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); + +- if (joinMessage != null && joinMessage.length() > 0) { +- for (IChatBaseComponent line : org.bukkit.craftbukkit.util.CraftChatMessage.fromString(joinMessage)) { +- server.getPlayerList().sendAll(new PacketPlayOutChat(line, ChatMessageType.SYSTEM, SystemUtils.b)); +- } ++ if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure ++ joinMessage = PaperAdventure.asVanilla(jm); // Paper - Adventure ++ server.getPlayerList().sendAll(new PacketPlayOutChat(joinMessage, ChatMessageType.SYSTEM, SystemUtils.b)); // Paper - Adventure + } + // CraftBukkit end + +@@ -473,7 +474,7 @@ public abstract class PlayerList { + + } + +- public String disconnect(EntityPlayer entityplayer) { // CraftBukkit - return string ++ public net.kyori.adventure.text.Component disconnect(EntityPlayer entityplayer) { // Paper - return Component + WorldServer worldserver = entityplayer.getWorldServer(); + + entityplayer.a(StatisticList.LEAVE_GAME); +@@ -484,7 +485,7 @@ public abstract class PlayerList { + entityplayer.closeInventory(); + } + +- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), "\u00A7e" + entityplayer.getName() + " left the game"); ++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getName()))); + cserver.getPluginManager().callEvent(playerQuitEvent); + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + +@@ -545,7 +546,7 @@ public abstract class PlayerList { + cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); + // CraftBukkit end + +- return playerQuitEvent.getQuitMessage(); // CraftBukkit ++ return playerQuitEvent.quitMessage(); // Paper - Adventure + } + + // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer +@@ -591,10 +592,10 @@ public abstract class PlayerList { + } + + // return chatmessage; +- if (!gameprofilebanentry.hasExpired()) event.disallow(PlayerLoginEvent.Result.KICK_BANNED, CraftChatMessage.fromComponent(chatmessage)); // Spigot ++ if (!gameprofilebanentry.hasExpired()) event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(chatmessage)); // Spigot // Paper - Adventure + } else if (!this.isWhitelisted(gameprofile)) { + chatmessage = new ChatMessage("multiplayer.disconnect.not_whitelisted"); +- event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, org.spigotmc.SpigotConfig.whitelistMessage); // Spigot ++ event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, PaperAdventure.LEGACY_SECTION_UXRC.deserialize(org.spigotmc.SpigotConfig.whitelistMessage)); // Spigot // Paper - Adventure + } else if (getIPBans().isBanned(socketaddress) && !getIPBans().get(socketaddress).hasExpired()) { + IpBanEntry ipbanentry = this.l.get(socketaddress); + +@@ -604,17 +605,17 @@ public abstract class PlayerList { + } + + // return chatmessage; +- event.disallow(PlayerLoginEvent.Result.KICK_BANNED, CraftChatMessage.fromComponent(chatmessage)); ++ event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure + } else { + // return this.players.size() >= this.maxPlayers && !this.f(gameprofile) ? new ChatMessage("multiplayer.disconnect.server_full") : null; + if (this.players.size() >= this.maxPlayers && !this.f(gameprofile)) { +- event.disallow(PlayerLoginEvent.Result.KICK_FULL, org.spigotmc.SpigotConfig.serverFullMessage); // Spigot ++ event.disallow(PlayerLoginEvent.Result.KICK_FULL, PaperAdventure.LEGACY_SECTION_UXRC.deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure + } + } + + cserver.getPluginManager().callEvent(event); + if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { +- loginlistener.disconnect(event.getKickMessage()); ++ loginlistener.disconnect(PaperAdventure.asVanilla(event.kickMessage())); // Paper - Adventure + return null; + } + return entity; +@@ -1135,7 +1136,7 @@ public abstract class PlayerList { + public void shutdown() { + // CraftBukkit start - disconnect safely + for (EntityPlayer player : this.players) { +- player.playerConnection.disconnect(this.server.server.getShutdownMessage()); // CraftBukkit - add custom shutdown message ++ player.playerConnection.disconnect(PaperAdventure.asVanilla(this.server.server.shutdownMessage())); // CraftBukkit - add custom shutdown message // Paper - Adventure + } + // CraftBukkit end + +diff --git a/src/main/java/net/minecraft/world/BossBattle.java b/src/main/java/net/minecraft/world/BossBattle.java +index e3c21e73f54c7312dd2e260079d727224fc08256..471085c9c846826ab3dda25c330b8708133784fe 100644 +--- a/src/main/java/net/minecraft/world/BossBattle.java ++++ b/src/main/java/net/minecraft/world/BossBattle.java +@@ -1,5 +1,6 @@ + package net.minecraft.world; + ++import io.papermc.paper.adventure.PaperAdventure; + import java.util.UUID; + import net.minecraft.EnumChatFormat; + import net.minecraft.network.chat.IChatBaseComponent; +@@ -14,6 +15,7 @@ public abstract class BossBattle { + protected boolean e; + protected boolean f; + protected boolean g; ++ public net.kyori.adventure.bossbar.BossBar adventure; // Paper + + public BossBattle(UUID uuid, IChatBaseComponent ichatbasecomponent, BossBattle.BarColor bossbattle_barcolor, BossBattle.BarStyle bossbattle_barstyle) { + this.h = uuid; +@@ -28,61 +30,75 @@ public abstract class BossBattle { + } + + public IChatBaseComponent j() { ++ if(this.adventure != null) return PaperAdventure.asVanilla(this.adventure.name()); // Paper + return this.title; + } + + public void a(IChatBaseComponent ichatbasecomponent) { ++ if (this.adventure != null) this.adventure.name(PaperAdventure.asAdventure(ichatbasecomponent)); // Paper + this.title = ichatbasecomponent; + } + + public float getProgress() { ++ if (this.adventure != null) return this.adventure.progress(); // Paper + return this.b; + } + + public void setProgress(float f) { ++ if (this.adventure != null) this.adventure.progress(f); // Paper + this.b = f; + } + + public BossBattle.BarColor l() { ++ if (this.adventure != null) return PaperAdventure.asVanilla(this.adventure.color()); // Paper + return this.color; + } + + public void a(BossBattle.BarColor bossbattle_barcolor) { ++ if(this.adventure != null) this.adventure.color(PaperAdventure.asAdventure(bossbattle_barcolor)); // Paper + this.color = bossbattle_barcolor; + } + + public BossBattle.BarStyle m() { ++ if(this.adventure != null) return PaperAdventure.asVanilla(this.adventure.overlay()); // Paper + return this.style; + } + + public void a(BossBattle.BarStyle bossbattle_barstyle) { ++ if(this.adventure != null) this.adventure.overlay(PaperAdventure.asAdventure(bossbattle_barstyle)); // Paper + this.style = bossbattle_barstyle; + } + + public boolean isDarkenSky() { ++ if(this.adventure != null) return this.adventure.hasFlag(net.kyori.adventure.bossbar.BossBar.Flag.DARKEN_SCREEN); // Paper + return this.e; + } + + public BossBattle setDarkenSky(boolean flag) { ++ if(this.adventure != null) PaperAdventure.setFlag(this.adventure, net.kyori.adventure.bossbar.BossBar.Flag.DARKEN_SCREEN, flag); // Paper + this.e = flag; + return this; + } + + public boolean isPlayMusic() { ++ if(this.adventure != null) return this.adventure.hasFlag(net.kyori.adventure.bossbar.BossBar.Flag.PLAY_BOSS_MUSIC); // Paper + return this.f; + } + + public BossBattle setPlayMusic(boolean flag) { ++ if(this.adventure != null) PaperAdventure.setFlag(this.adventure, net.kyori.adventure.bossbar.BossBar.Flag.PLAY_BOSS_MUSIC, flag); // Paper + this.f = flag; + return this; + } + + public BossBattle setCreateFog(boolean flag) { ++ if(this.adventure != null) PaperAdventure.setFlag(this.adventure, net.kyori.adventure.bossbar.BossBar.Flag.CREATE_WORLD_FOG, flag); // Paper + this.g = flag; + return this; + } + + public boolean isCreateFog() { ++ if(this.adventure != null) return this.adventure.hasFlag(net.kyori.adventure.bossbar.BossBar.Flag.CREATE_WORLD_FOG); // Paper + return this.g; + } + +diff --git a/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java +index 0134bbda9e6fc900b7eefa05442e25539bab3431..b76ef55145336cc8dc4857b79767f5a738ad5144 100644 +--- a/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java ++++ b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java +@@ -94,6 +94,7 @@ public abstract class Enchantment { + return this.f(); + } + ++ public final IChatBaseComponent getTranslationComponentForLevel(int level) { return this.d(level); } // Paper - OBFHELPER + public IChatBaseComponent d(int i) { + ChatMessage chatmessage = new ChatMessage(this.g()); + +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java b/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java +index 7ec93ddd7e7c9dc54e3e4dcfe0d1654c0b0a8536..3f057f0bd23bc1c693c8f04ee8acd6626c620008 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java +@@ -32,6 +32,7 @@ import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + + // CraftBukkit start ++import io.papermc.paper.adventure.PaperAdventure; // Paper + import java.util.UUID; + + import org.bukkit.craftbukkit.CraftServer; +@@ -473,7 +474,7 @@ public class WorldMap extends PersistentBase { + for ( org.bukkit.map.MapCursor cursor : render.cursors) { + + if (cursor.isVisible()) { +- icons.add(new MapIcon(MapIcon.Type.a(cursor.getRawType()), cursor.getX(), cursor.getY(), cursor.getDirection(), CraftChatMessage.fromStringOrNull(cursor.getCaption()))); ++ icons.add(new MapIcon(MapIcon.Type.a(cursor.getRawType()), cursor.getX(), cursor.getY(), cursor.getDirection(), PaperAdventure.asVanilla(cursor.caption()))); // Paper - Adventure + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 6d68610d8ffdef4357cf61ac2f4cf6bf54157b15..2f6b49e3ae7a7407eaec9810af8bf72e3a4896de 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -563,8 +563,11 @@ public final class CraftServer implements Server { + } + + @Override ++ @Deprecated // Paper start + public int broadcastMessage(String message) { +- return broadcast(message, BROADCAST_CHANNEL_USERS); ++ this.sendMessage(io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(message)); ++ return this.getOnlinePlayers().size() + 1; ++ // Paper end + } + + public Player getPlayer(final EntityPlayer entity) { +@@ -1302,7 +1305,15 @@ public final class CraftServer implements Server { + return configuration.getInt("settings.spawn-radius", -1); + } + ++ // Paper start + @Override ++ public net.kyori.adventure.text.Component shutdownMessage() { ++ String msg = getShutdownMessage(); ++ return msg != null ? io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(msg) : null; ++ } ++ // Paper end ++ @Override ++ @Deprecated // Paper + public String getShutdownMessage() { + return configuration.getString("settings.shutdown-message"); + } +@@ -1418,7 +1429,15 @@ public final class CraftServer implements Server { + } + + @Override ++ @Deprecated // Paper + public int broadcast(String message, String permission) { ++ // Paper start - Adventure ++ return this.broadcast(io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(message), permission); ++ } ++ ++ @Override ++ public int broadcast(net.kyori.adventure.text.Component message, String permission) { ++ // Paper end + Set recipients = new HashSet<>(); + for (Permissible permissible : getPluginManager().getPermissionSubscriptions(permission)) { + if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { +@@ -1426,14 +1445,14 @@ public final class CraftServer implements Server { + } + } + +- BroadcastMessageEvent broadcastMessageEvent = new BroadcastMessageEvent(!Bukkit.isPrimaryThread(), message, recipients); ++ BroadcastMessageEvent broadcastMessageEvent = new BroadcastMessageEvent(!Bukkit.isPrimaryThread(), message, recipients); // Paper - Adventure + getPluginManager().callEvent(broadcastMessageEvent); + + if (broadcastMessageEvent.isCancelled()) { + return 0; + } + +- message = broadcastMessageEvent.getMessage(); ++ message = broadcastMessageEvent.message(); // Paper - Adventure + + for (CommandSender recipient : recipients) { + recipient.sendMessage(message); +@@ -1659,6 +1678,14 @@ public final class CraftServer implements Server { + return CraftInventoryCreator.INSTANCE.createInventory(owner, type); + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ Validate.isTrue(type.isCreatable(), "Cannot open an inventory of type ", type); ++ return CraftInventoryCreator.INSTANCE.createInventory(owner, type, title); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) { + Validate.isTrue(type.isCreatable(), "Cannot open an inventory of type ", type); +@@ -1671,13 +1698,28 @@ public final class CraftServer implements Server { + return CraftInventoryCreator.INSTANCE.createInventory(owner, size); + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, int size, net.kyori.adventure.text.Component title) throws IllegalArgumentException { ++ Validate.isTrue(9 <= size && size <= 54 && size % 9 == 0, "Size for custom inventory must be a multiple of 9 between 9 and 54 slots (got " + size + ")"); ++ return CraftInventoryCreator.INSTANCE.createInventory(owner, size, title); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder owner, int size, String title) throws IllegalArgumentException { + Validate.isTrue(9 <= size && size <= 54 && size % 9 == 0, "Size for custom inventory must be a multiple of 9 between 9 and 54 slots (got " + size + ")"); + return CraftInventoryCreator.INSTANCE.createInventory(owner, size, title); + } + ++ // Paper start + @Override ++ public Merchant createMerchant(net.kyori.adventure.text.Component title) { ++ return new org.bukkit.craftbukkit.inventory.CraftMerchantCustom(title == null ? InventoryType.MERCHANT.defaultTitle() : title); ++ } ++ // Paper end ++ @Override ++ @Deprecated // Paper + public Merchant createMerchant(String title) { + return new CraftMerchantCustom(title == null ? InventoryType.MERCHANT.getDefaultTitle() : title); + } +@@ -1721,6 +1763,12 @@ public final class CraftServer implements Server { + return Thread.currentThread().equals(console.serverThread) || console.hasStopped() || !org.spigotmc.AsyncCatcher.enabled; // All bets are off if we have shut down (e.g. due to watchdog) + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component motd() { ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(new net.minecraft.network.chat.ChatComponentText(console.getMotd())); ++ } ++ // Paper end + @Override + public String getMotd() { + return console.getMotd(); +@@ -2149,5 +2197,15 @@ public final class CraftServer implements Server { + return null; + } + } ++ ++ // Paper start ++ private Iterable adventure$audiences; ++ @Override ++ public Iterable audiences() { ++ if (this.adventure$audiences == null) { ++ this.adventure$audiences = com.google.common.collect.Iterables.concat(java.util.Collections.singleton(this.getConsoleSender()), this.getOnlinePlayers()); ++ } ++ return this.adventure$audiences; ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +index ae735836accc6bf6f0831f72ff882720b69df792..d3ae5cadd88f9012203d2c04cbe38af9b215ef0b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +@@ -70,6 +70,19 @@ public class CraftBeacon extends CraftBlockEntityState impleme + this.getSnapshot().secondaryEffect = (effect != null) ? MobEffectList.fromId(effect.getId()) : null; + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component customName() { ++ final TileEntityBeacon be = this.getSnapshot(); ++ return be.customName != null ? io.papermc.paper.adventure.PaperAdventure.asAdventure(be.customName) : null; ++ } ++ ++ @Override ++ public void customName(final net.kyori.adventure.text.Component customName) { ++ this.getSnapshot().setCustomName(customName != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(customName) : null); ++ } ++ // Paper end ++ + @Override + public String getCustomName() { + TileEntityBeacon beacon = this.getSnapshot(); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java +index e8ce890c551c9b809e8ba3f7449dc33f3a3a6b80..c99a59573653ee5d14e780137c769116bf781ea7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java +@@ -32,6 +32,19 @@ public abstract class CraftContainer extends Craf + this.getSnapshot().chestLock = (key == null) ? ChestLock.a : new ChestLock(key); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component customName() { ++ final T be = this.getSnapshot(); ++ return be.hasCustomName() ? io.papermc.paper.adventure.PaperAdventure.asAdventure(be.getCustomName()) : null; ++ } ++ ++ @Override ++ public void customName(final net.kyori.adventure.text.Component customName) { ++ this.getSnapshot().setCustomName(customName != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(customName) : null); ++ } ++ // Paper end ++ + @Override + public String getCustomName() { + T container = this.getSnapshot(); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftEnchantingTable.java b/src/main/java/org/bukkit/craftbukkit/block/CraftEnchantingTable.java +index a559a77aae870988f4dd5e6f5f1f08feb3fad054..75ee5cc96c0e54f99e2ce820289bb74f57c426a2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftEnchantingTable.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftEnchantingTable.java +@@ -16,6 +16,19 @@ public class CraftEnchantingTable extends CraftBlockEntityState implements Sign { + + // Lazily initialized only if requested: +- private String[] originalLines = null; +- private String[] lines = null; ++ // Paper start ++ private java.util.ArrayList originalLines = null; // ArrayList for RandomAccess ++ private java.util.ArrayList lines = null; // ArrayList for RandomAccess ++ // Paper end + + public CraftSign(final Block block) { + super(block, TileEntitySign.class); +@@ -24,27 +26,52 @@ public class CraftSign extends CraftBlockEntityState implements + super(material, te); + } + ++ // Paper start + @Override +- public String[] getLines() { +- if (lines == null) { +- // Lazy initialization: +- TileEntitySign sign = this.getSnapshot(); +- lines = new String[sign.lines.length]; +- System.arraycopy(revertComponents(sign.lines), 0, lines, 0, lines.length); +- originalLines = new String[lines.length]; +- System.arraycopy(lines, 0, originalLines, 0, originalLines.length); ++ public java.util.List lines() { ++ this.loadLines(); ++ return this.lines; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.Component line(int index) { ++ this.loadLines(); ++ return this.lines.get(index); ++ } ++ ++ @Override ++ public void line(int index, net.kyori.adventure.text.Component line) { ++ this.loadLines(); ++ this.lines.set(index, line); ++ } ++ ++ private void loadLines() { ++ if (lines != null) { ++ return; + } +- return lines; ++ ++ // Lazy initialization: ++ TileEntitySign sign = this.getSnapshot(); ++ lines = io.papermc.paper.adventure.PaperAdventure.asAdventure(com.google.common.collect.Lists.newArrayList(sign.lines)); ++ originalLines = new java.util.ArrayList<>(lines); ++ } ++ // Paper end ++ @Override ++ public String[] getLines() { ++ this.loadLines(); ++ return this.lines.stream().map(io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC::serialize).toArray(String[]::new); // Paper + } + + @Override + public String getLine(int index) throws IndexOutOfBoundsException { +- return getLines()[index]; ++ this.loadLines(); ++ return io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(this.lines.get(index)); // Paper + } + + @Override + public void setLine(int index, String line) throws IndexOutOfBoundsException { +- getLines()[index] = line; ++ this.loadLines(); ++ this.lines.set(index, line != null ? io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(line) : net.kyori.adventure.text.Component.empty()); // Paper + } + + @Override +@@ -72,16 +99,32 @@ public class CraftSign extends CraftBlockEntityState implements + super.applyTo(sign); + + if (lines != null) { +- for (int i = 0; i < lines.length; i++) { +- String line = (lines[i] == null) ? "" : lines[i]; +- if (line.equals(originalLines[i])) { ++ // Paper start ++ for (int i = 0; i < this.lines.size(); ++i) { ++ net.kyori.adventure.text.Component component = this.lines.get(i); ++ net.kyori.adventure.text.Component origComp = this.originalLines.get(i); ++ if (component.equals(origComp)) { + continue; // The line contents are still the same, skip. + } +- sign.lines[i] = CraftChatMessage.fromString(line)[0]; ++ sign.lines[i] = io.papermc.paper.adventure.PaperAdventure.asVanilla(component); + } ++ // Paper end + } + } + ++ // Paper start ++ public static IChatBaseComponent[] sanitizeLines(java.util.List lines) { ++ IChatBaseComponent[] components = new IChatBaseComponent[4]; ++ for (int i = 0; i < 4; i++) { ++ if (i < lines.size() && lines.get(i) != null) { ++ components[i] = io.papermc.paper.adventure.PaperAdventure.asVanilla(lines.get(i)); ++ } else { ++ components[i] = new ChatComponentText(""); ++ } ++ } ++ return components; ++ } ++ // Paper end + public static IChatBaseComponent[] sanitizeLines(String[] lines) { + IChatBaseComponent[] components = new IChatBaseComponent[4]; + +diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +index 089fe4a3458ed3106fa214f89a7004a5d3c6bb95..af986adfdb547cb61fbd52f0f89858f1a9e52cc3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +@@ -80,4 +80,11 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co + public boolean isConversing() { + return conversationTracker.isConversing(); + } ++ ++ // Paper start ++ @Override ++ public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { ++ this.sendRawMessage(org.bukkit.craftbukkit.util.CraftChatMessage.fromComponent(io.papermc.paper.adventure.PaperAdventure.asVanilla(message))); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +index 8f694de2fb3827b9be0dd768ad90eb72c7d708e4..5a14430f63894bbe9daa42900cf5a6519bea4f45 100644 +--- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java ++++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +@@ -187,6 +187,12 @@ public class CraftEnchantment extends Enchantment { + CraftEnchantment ench = (CraftEnchantment) other; + return !target.isCompatible(ench.target); + } ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component displayName(int level) { ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(getHandle().getTranslationComponentForLevel(level)); ++ } ++ // Paper end + + public net.minecraft.world.item.enchantment.Enchantment getHandle() { + return target; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index eea242af23825ad29ada6e997205e87edffb6bb9..3cf81734c8580f4d88ea97b6ac737a370b413c84 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -768,6 +768,19 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return getHandle().getVehicle().getBukkitEntity(); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component customName() { ++ final IChatBaseComponent name = this.getHandle().getCustomName(); ++ return name != null ? io.papermc.paper.adventure.PaperAdventure.asAdventure(name) : null; ++ } ++ ++ @Override ++ public void customName(final net.kyori.adventure.text.Component customName) { ++ this.getHandle().setCustomName(customName != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(customName) : null); ++ } ++ // Paper end ++ + @Override + public void setCustomName(String name) { + // sane limit for name length +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 3a4e2261d0b0cd17df3f96e75f3c509156679c48..105d0388998d1e35e634d2163fe1a44aa7037ac8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -316,9 +316,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + container = CraftEventFactory.callInventoryOpenEvent(player, container); + if (container == null) return; + +- String title = container.getBukkitView().getTitle(); ++ //String title = container.getBukkitView().getTitle(); // Paper - comment ++ net.kyori.adventure.text.Component adventure$title = container.getBukkitView().title(); // Paper ++ if (adventure$title == null) adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(container.getBukkitView().getTitle()); // Paper + +- player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); ++ //player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); // Paper // Paper - comment ++ player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper + getHandle().activeContainer = container; + getHandle().activeContainer.addSlotListener(player); + } +@@ -387,8 +390,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + + // Now open the window + Containers windowType = CraftContainer.getNotchInventoryType(inventory.getTopInventory()); +- String title = inventory.getTitle(); +- player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); ++ ++ //String title = inventory.getTitle(); // Paper - comment ++ net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper ++ if (adventure$title == null) adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(inventory.getTitle()); // Paper ++ //player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment ++ player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper + player.activeContainer = container; + player.activeContainer.addSlotListener(player); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 597b3b061c707081e7665d5896f5d73676e691d6..137870c7d18c9ef3ae637e83c5457d42ec40c669 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -240,14 +240,39 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public String getDisplayName() { ++ if(true) return io.papermc.paper.adventure.DisplayNames.getLegacy(this); // Paper + return getHandle().displayName; + } + + @Override + public void setDisplayName(final String name) { ++ this.getHandle().adventure$displayName = name != null ? io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(name) : net.kyori.adventure.text.Component.text(this.getName()); // Paper + getHandle().displayName = name == null ? getName() : name; + } + ++ // Paper start ++ @Override ++ public void playerListName(net.kyori.adventure.text.Component name) { ++ getHandle().listName = name == null ? null : io.papermc.paper.adventure.PaperAdventure.asVanilla(name); ++ for (EntityPlayer player : server.getHandle().players) { ++ if (player.getBukkitEntity().canSee(this)) { ++ player.playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, getHandle())); ++ } ++ } ++ } ++ @Override ++ public net.kyori.adventure.text.Component playerListName() { ++ return getHandle().listName == null ? net.kyori.adventure.text.Component.text(getName()) : io.papermc.paper.adventure.PaperAdventure.asAdventure(getHandle().listName); ++ } ++ @Override ++ public net.kyori.adventure.text.Component playerListHeader() { ++ return playerListHeader; ++ } ++ @Override ++ public net.kyori.adventure.text.Component playerListFooter() { ++ return playerListFooter; ++ } ++ // Paper end + @Override + public String getPlayerListName() { + return getHandle().listName == null ? getName() : CraftChatMessage.fromComponent(getHandle().listName); +@@ -266,35 +291,35 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + } + +- private IChatBaseComponent playerListHeader; +- private IChatBaseComponent playerListFooter; ++ private net.kyori.adventure.text.Component playerListHeader; // Paper - Adventure ++ private net.kyori.adventure.text.Component playerListFooter; // Paper - Adventure + + @Override + public String getPlayerListHeader() { +- return (playerListHeader == null) ? null : CraftChatMessage.fromComponent(playerListHeader); ++ return (playerListHeader == null) ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(playerListHeader); // Paper - Adventure + } + + @Override + public String getPlayerListFooter() { +- return (playerListFooter == null) ? null : CraftChatMessage.fromComponent(playerListFooter); ++ return (playerListFooter == null) ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(playerListFooter); // Paper - Adventure + } + + @Override + public void setPlayerListHeader(String header) { +- this.playerListHeader = CraftChatMessage.fromStringOrNull(header, true); ++ this.playerListHeader = header == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(header); // Paper - Adventure + updatePlayerListHeaderFooter(); + } + + @Override + public void setPlayerListFooter(String footer) { +- this.playerListFooter = CraftChatMessage.fromStringOrNull(footer, true); ++ this.playerListFooter = footer == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(footer); // Paper - Adventure + updatePlayerListHeaderFooter(); + } + + @Override + public void setPlayerListHeaderFooter(String header, String footer) { +- this.playerListHeader = CraftChatMessage.fromStringOrNull(header, true); +- this.playerListFooter = CraftChatMessage.fromStringOrNull(footer, true); ++ this.playerListHeader = header == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(header); // Paper - Adventure ++ this.playerListFooter = footer == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(footer); // Paper - Adventure + updatePlayerListHeaderFooter(); + } + +@@ -302,8 +327,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + if (getHandle().playerConnection == null) return; + + PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter(); +- packet.header = (this.playerListHeader == null) ? new ChatComponentText("") : this.playerListHeader; +- packet.footer = (this.playerListFooter == null) ? new ChatComponentText("") : this.playerListFooter; ++ packet.header = (this.playerListHeader == null) ? new ChatComponentText("") : io.papermc.paper.adventure.PaperAdventure.asVanilla(this.playerListHeader); // Paper - Adventure ++ packet.footer = (this.playerListFooter == null) ? new ChatComponentText("") : io.papermc.paper.adventure.PaperAdventure.asVanilla(this.playerListFooter); // Paper - Adventure + getHandle().playerConnection.sendPacket(packet); + } + +@@ -335,6 +360,19 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + getHandle().playerConnection.disconnect(message == null ? "" : message); + } + ++ // Paper start ++ @Override ++ public void kick(final net.kyori.adventure.text.Component message) { ++ org.spigotmc.AsyncCatcher.catchOp("player kick"); ++ final PlayerConnection connection = this.getHandle().playerConnection; ++ if (connection != null) { ++ connection.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla( ++ message == null ? net.kyori.adventure.text.Component.empty() : message ++ )); ++ } ++ } ++ // Paper end ++ + @Override + public void setCompassTarget(Location loc) { + if (getHandle().playerConnection == null) return; +@@ -561,6 +599,37 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + getHandle().playerConnection.sendPacket(packet); + } + ++ // Paper start ++ @Override ++ public void sendSignChange(Location loc, List lines) { ++ this.sendSignChange(loc, lines, org.bukkit.DyeColor.BLACK); ++ } ++ @Override ++ public void sendSignChange(Location loc, List lines, DyeColor dyeColor) { ++ if (getHandle().playerConnection == null) { ++ return; ++ } ++ if (lines == null) { ++ lines = new java.util.ArrayList<>(4); ++ } ++ Validate.notNull(loc, "Location cannot be null"); ++ Validate.notNull(dyeColor, "DyeColor cannot be null"); ++ if (lines.size() < 4) { ++ throw new IllegalArgumentException("Must have at least 4 lines"); ++ } ++ IChatBaseComponent[] components = CraftSign.sanitizeLines(lines); ++ this.sendSignChange0(components, loc, dyeColor); ++ } ++ ++ private void sendSignChange0(IChatBaseComponent[] components, Location loc, DyeColor dyeColor) { ++ TileEntitySign sign = new TileEntitySign(); ++ sign.setPosition(new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); ++ sign.setColor(EnumColor.fromColorIndex(dyeColor.getWoolData())); ++ System.arraycopy(components, 0, sign.lines, 0, sign.lines.length); ++ ++ getHandle().playerConnection.sendPacket(sign.getUpdatePacket()); ++ } ++ // Paper end + @Override + public void sendSignChange(Location loc, String[] lines) { + sendSignChange(loc, lines, DyeColor.BLACK); +@@ -583,12 +652,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + IChatBaseComponent[] components = CraftSign.sanitizeLines(lines); +- TileEntitySign sign = new TileEntitySign(); ++ /*TileEntitySign sign = new TileEntitySign(); // Paper + sign.setPosition(new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + sign.setColor(EnumColor.fromColorIndex(dyeColor.getWoolData())); + System.arraycopy(components, 0, sign.lines, 0, sign.lines.length); + +- getHandle().playerConnection.sendPacket(sign.getUpdatePacket()); ++ getHandle().playerConnection.sendPacket(sign.getUpdatePacket());*/ // Paper ++ this.sendSignChange0(components, loc, dyeColor); // Paper + } + + @Override +@@ -1688,6 +1758,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return (getHandle().clientViewDistance == null) ? Bukkit.getViewDistance() : getHandle().clientViewDistance; + } + ++ // Paper start ++ @Override ++ public java.util.Locale locale() { ++ return getHandle().adventure$locale; ++ } ++ // Paper end + @Override + public String getLocale() { + return getHandle().locale; +@@ -1711,6 +1787,138 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + getInventory().setItemInMainHand(hand); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component displayName() { ++ return this.getHandle().adventure$displayName; ++ } ++ ++ @Override ++ public void displayName(final net.kyori.adventure.text.Component displayName) { ++ this.getHandle().adventure$displayName = displayName != null ? displayName : net.kyori.adventure.text.Component.text(this.getName()); ++ this.getHandle().displayName = null; ++ } ++ ++ @Override ++ public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { ++ final PacketPlayOutChat packet = new PacketPlayOutChat(null, type == net.kyori.adventure.audience.MessageType.CHAT ? net.minecraft.network.chat.ChatMessageType.CHAT : net.minecraft.network.chat.ChatMessageType.SYSTEM, identity.uuid()); ++ packet.adventure$message = message; ++ this.getHandle().playerConnection.sendPacket(packet); ++ } ++ ++ @Override ++ public void sendActionBar(final net.kyori.adventure.text.Component message) { ++ final PacketPlayOutTitle packet = new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.ACTIONBAR, null); ++ packet.adventure$text = message; ++ this.getHandle().playerConnection.sendPacket(packet); ++ } ++ ++ @Override ++ public void sendPlayerListHeader(final net.kyori.adventure.text.Component header) { ++ this.playerListHeader = header; ++ this.adventure$sendPlayerListHeaderAndFooter(); ++ } ++ ++ @Override ++ public void sendPlayerListFooter(final net.kyori.adventure.text.Component footer) { ++ this.playerListFooter = footer; ++ this.adventure$sendPlayerListHeaderAndFooter(); ++ } ++ ++ @Override ++ public void sendPlayerListHeaderAndFooter(final net.kyori.adventure.text.Component header, final net.kyori.adventure.text.Component footer) { ++ this.playerListHeader = header; ++ this.playerListFooter = footer; ++ this.adventure$sendPlayerListHeaderAndFooter(); ++ } ++ ++ private void adventure$sendPlayerListHeaderAndFooter() { ++ final PlayerConnection connection = this.getHandle().playerConnection; ++ if (connection == null) return; ++ final PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter(); ++ packet.adventure$header = (this.playerListHeader == null) ? net.kyori.adventure.text.Component.empty() : this.playerListHeader; ++ packet.adventure$footer = (this.playerListFooter == null) ? net.kyori.adventure.text.Component.empty() : this.playerListFooter; ++ connection.sendPacket(packet); ++ } ++ ++ @Override ++ public void showTitle(final net.kyori.adventure.title.Title title) { ++ final PlayerConnection connection = this.getHandle().playerConnection; ++ final net.kyori.adventure.title.Title.Times times = title.times(); ++ if (times != null) { ++ connection.sendPacket(new PacketPlayOutTitle(ticks(times.fadeIn()), ticks(times.stay()), ticks(times.fadeOut()))); ++ } ++ final PacketPlayOutTitle sp = new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.SUBTITLE, null); ++ sp.adventure$text = title.subtitle(); ++ connection.sendPacket(sp); ++ final PacketPlayOutTitle tp = new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.TITLE, null); ++ tp.adventure$text = title.title(); ++ connection.sendPacket(tp); ++ } ++ ++ private static int ticks(final java.time.Duration duration) { ++ if (duration == null) { ++ return -1; ++ } ++ return (int) (duration.toMillis() / 50L); ++ } ++ ++ @Override ++ public void clearTitle() { ++ this.getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.CLEAR, null)); ++ } ++ ++ // resetTitle implemented above ++ ++ @Override ++ public void showBossBar(final net.kyori.adventure.bossbar.BossBar bar) { ++ ((net.kyori.adventure.bossbar.HackyBossBarPlatformBridge) bar).paper$playerShow(this); ++ } ++ ++ @Override ++ public void hideBossBar(final net.kyori.adventure.bossbar.BossBar bar) { ++ ((net.kyori.adventure.bossbar.HackyBossBarPlatformBridge) bar).paper$playerHide(this); ++ } ++ ++ @Override ++ public void playSound(final net.kyori.adventure.sound.Sound sound) { ++ final Vec3D pos = this.getHandle().getPositionVector(); ++ this.playSound(sound, pos.x, pos.y, pos.z); ++ } ++ ++ @Override ++ public void playSound(final net.kyori.adventure.sound.Sound sound, final double x, final double y, final double z) { ++ final MinecraftKey name = io.papermc.paper.adventure.PaperAdventure.asVanilla(sound.name()); ++ final java.util.Optional event = net.minecraft.core.IRegistry.SOUND_EVENT.getOptional(name); ++ if (event.isPresent()) { ++ this.getHandle().playerConnection.sendPacket(new PacketPlayOutNamedSoundEffect(event.get(), io.papermc.paper.adventure.PaperAdventure.asVanilla(sound.source()), x, y, z, sound.volume(), sound.pitch())); ++ } else { ++ this.getHandle().playerConnection.sendPacket(new PacketPlayOutCustomSoundEffect(name, io.papermc.paper.adventure.PaperAdventure.asVanilla(sound.source()), new Vec3D(x, y, z), sound.volume(), sound.pitch())); ++ } ++ } ++ ++ @Override ++ public void stopSound(final net.kyori.adventure.sound.SoundStop stop) { ++ this.getHandle().playerConnection.sendPacket(new PacketPlayOutStopSound( ++ io.papermc.paper.adventure.PaperAdventure.asVanillaNullable(stop.sound()), ++ io.papermc.paper.adventure.PaperAdventure.asVanillaNullable(stop.source()) ++ )); ++ } ++ ++ @Override ++ public void openBook(final net.kyori.adventure.inventory.Book book) { ++ final java.util.Locale locale = this.getHandle().adventure$locale; ++ final net.minecraft.world.item.ItemStack item = io.papermc.paper.adventure.PaperAdventure.asItemStack(book, locale); ++ final EntityPlayer player = this.getHandle(); ++ final PlayerConnection connection = player.playerConnection; ++ final net.minecraft.world.entity.player.PlayerInventory inventory = player.inventory; ++ final int slot = inventory.items.size() + inventory.itemInHandIndex; ++ connection.sendPacket(new net.minecraft.network.protocol.game.PacketPlayOutSetSlot(0, slot, item)); ++ connection.sendPacket(new net.minecraft.network.protocol.game.PacketPlayOutOpenBook(net.minecraft.world.EnumHand.MAIN_HAND)); ++ connection.sendPacket(new net.minecraft.network.protocol.game.PacketPlayOutSetSlot(0, slot, inventory.getItemInHand())); ++ } ++ // Paper end ++ + // Spigot start + private final Player.Spigot spigot = new Player.Spigot() + { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index c925582b84b6576e869a93874da8ef94ca26a39c..9175d66ec9ad2e8155d6dea64beadfa73b93cd48 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -767,9 +767,9 @@ public class CraftEventFactory { + return event; + } + +- public static PlayerDeathEvent callPlayerDeathEvent(EntityPlayer victim, List drops, String deathMessage, boolean keepInventory) { ++ public static PlayerDeathEvent callPlayerDeathEvent(EntityPlayer victim, List drops, net.kyori.adventure.text.Component deathMessage, String stringDeathMessage, boolean keepInventory) { // Paper - Adventure + CraftPlayer entity = victim.getBukkitEntity(); +- PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage); ++ PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage, stringDeathMessage); // Paper - Adventure + event.setKeepInventory(keepInventory); + org.bukkit.World world = entity.getWorld(); + Bukkit.getServer().getPluginManager().callEvent(event); +@@ -793,7 +793,7 @@ public class CraftEventFactory { + * Server methods + */ + public static ServerListPingEvent callServerListPingEvent(Server craftServer, InetAddress address, String motd, int numPlayers, int maxPlayers) { +- ServerListPingEvent event = new ServerListPingEvent(address, motd, numPlayers, maxPlayers); ++ ServerListPingEvent event = new ServerListPingEvent(address, craftServer.motd(), numPlayers, maxPlayers); // Paper - Adventure + craftServer.getPluginManager().callEvent(event); + return event; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +index 384520dd734449d4e4f5243fbaad5f666b0c965c..614ab2d73db2293116f2272f6cd5c16da446132d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +@@ -39,6 +39,7 @@ public class CraftContainer extends Container { + + private final InventoryView view; + private InventoryType cachedType; ++ private net.kyori.adventure.text.Component adventure$title; // Paper + private String cachedTitle; + private Container delegate; + private final int cachedSize; +@@ -50,7 +51,9 @@ public class CraftContainer extends Container { + IInventory top = ((CraftInventory) view.getTopInventory()).getInventory(); + PlayerInventory bottom = (PlayerInventory) ((CraftInventory) view.getBottomInventory()).getInventory(); + cachedType = view.getType(); +- cachedTitle = view.getTitle(); ++ this.adventure$title = view.title(); // Paper ++ if (this.adventure$title == null) this.adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(view.getTitle()); // Paper ++ //cachedTitle = view.getTitle(); // Paper - comment + cachedSize = getSize(); + setupSlots(top, bottom, player); + } +@@ -77,6 +80,13 @@ public class CraftContainer extends Container { + return inventory.getType(); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component title() { ++ return inventory instanceof CraftInventoryCustom ? ((CraftInventoryCustom.MinecraftInventory) ((CraftInventory) inventory).getInventory()).title() : net.kyori.adventure.text.Component.text(inventory.getType().getDefaultTitle()); ++ } ++ // Paper end ++ + @Override + public String getTitle() { + return inventory instanceof CraftInventoryCustom ? ((CraftInventoryCustom.MinecraftInventory) ((CraftInventory) inventory).getInventory()).getTitle() : inventory.getType().getDefaultTitle(); +@@ -95,7 +105,8 @@ public class CraftContainer extends Container { + + @Override + public boolean c(EntityHuman entityhuman) { +- if (cachedType == view.getType() && cachedSize == getSize() && cachedTitle.equals(view.getTitle())) { ++ if (cachedType == view.getType() && cachedSize == getSize() && this.adventure$title.equals(view.title())) { // Paper ++ //if (cachedType == view.getType() && cachedSize == getSize() && cachedTitle.equals(view.getTitle())) { // Paper - comment + return true; + } + // If the window type has changed for some reason, update the player +@@ -103,7 +114,9 @@ public class CraftContainer extends Container { + // as good a place as any to put something like this. + boolean typeChanged = (cachedType != view.getType()); + cachedType = view.getType(); +- cachedTitle = view.getTitle(); ++ this.adventure$title = view.title(); // Paper ++ if (this.adventure$title == null) this.adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(view.getTitle()); // Paper ++ //cachedTitle = view.getTitle(); // Paper - comment + if (view.getPlayer() instanceof CraftPlayer) { + CraftPlayer player = (CraftPlayer) view.getPlayer(); + Containers type = getNotchInventoryType(view.getTopInventory()); +@@ -115,7 +128,8 @@ public class CraftContainer extends Container { + setupSlots(top, bottom, player.getHandle()); + } + int size = getSize(); +- player.getHandle().playerConnection.sendPacket(new PacketPlayOutOpenWindow(this.windowId, type, new ChatComponentText(cachedTitle))); ++ player.getHandle().playerConnection.sendPacket(new PacketPlayOutOpenWindow(this.windowId, type, io.papermc.paper.adventure.PaperAdventure.asVanilla(this.adventure$title))); // Paper ++ //player.getHandle().playerConnection.sendPacket(new PacketPlayOutOpenWindow(this.windowId, type, new ChatComponentText(cachedTitle))); // Paper - comment + player.updateInventory(); + } + return true; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java +index a537cc4ac5d052168f96a1ae73b6b17a380436ab..21347cf02cc01c90a81e7dd8264ef11968d9f145 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java +@@ -19,6 +19,12 @@ public class CraftInventoryCustom extends CraftInventory { + super(new MinecraftInventory(owner, type)); + } + ++ // Paper start ++ public CraftInventoryCustom(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ super(new MinecraftInventory(owner, type, title)); ++ } ++ // Paper end ++ + public CraftInventoryCustom(InventoryHolder owner, InventoryType type, String title) { + super(new MinecraftInventory(owner, type, title)); + } +@@ -27,6 +33,12 @@ public class CraftInventoryCustom extends CraftInventory { + super(new MinecraftInventory(owner, size)); + } + ++ // Paper start ++ public CraftInventoryCustom(InventoryHolder owner, int size, net.kyori.adventure.text.Component title) { ++ super(new MinecraftInventory(owner, size, title)); ++ } ++ // Paper end ++ + public CraftInventoryCustom(InventoryHolder owner, int size, String title) { + super(new MinecraftInventory(owner, size, title)); + } +@@ -36,9 +48,17 @@ public class CraftInventoryCustom extends CraftInventory { + private int maxStack = MAX_STACK; + private final List viewers; + private final String title; ++ private final net.kyori.adventure.text.Component adventure$title; // Paper + private InventoryType type; + private final InventoryHolder owner; + ++ // Paper start ++ public MinecraftInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ this(owner, type.getDefaultSize(), title); ++ this.type = type; ++ } ++ // Paper end ++ + public MinecraftInventory(InventoryHolder owner, InventoryType type) { + this(owner, type.getDefaultSize(), type.getDefaultTitle()); + this.type = type; +@@ -57,11 +77,24 @@ public class CraftInventoryCustom extends CraftInventory { + Validate.notNull(title, "Title cannot be null"); + this.items = NonNullList.a(size, ItemStack.b); + this.title = title; ++ this.adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(title); + this.viewers = new ArrayList(); + this.owner = owner; + this.type = InventoryType.CHEST; + } + ++ // Paper start ++ public MinecraftInventory(final InventoryHolder owner, final int size, final net.kyori.adventure.text.Component title) { ++ Validate.notNull(title, "Title cannot be null"); ++ this.items = NonNullList.a(size, ItemStack.b); ++ this.title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(title); ++ this.adventure$title = title; ++ this.viewers = new ArrayList(); ++ this.owner = owner; ++ this.type = InventoryType.CHEST; ++ } ++ // Paper end ++ + @Override + public int getSize() { + return items.size(); +@@ -183,6 +216,12 @@ public class CraftInventoryCustom extends CraftInventory { + return null; + } + ++ // Paper start ++ public net.kyori.adventure.text.Component title() { ++ return this.adventure$title; ++ } ++ // Paper end ++ + public String getTitle() { + return title; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java +index 04073ed45f8068d80e58d3927b5ebc3160c6a8c6..9949bb8cac73b2f1f02b51079c0e244f923af8e9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java +@@ -64,6 +64,13 @@ public class CraftInventoryView extends InventoryView { + return CraftItemStack.asCraftMirror(container.getSlot(slot).getItem()); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component title() { ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.container.getTitle()); ++ } ++ // Paper end ++ + @Override + public String getTitle() { + return CraftChatMessage.fromComponent(container.getTitle()); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index 89a3617068421bb86baf4e8bfd9df2d0626adff7..6d320bbe0c75cc0df504faacf0f7d24804b90d5f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -334,4 +334,12 @@ public final class CraftItemFactory implements ItemFactory { + public Material updateMaterial(ItemMeta meta, Material material) throws IllegalArgumentException { + return ((CraftMetaItem) meta).updateMaterial(material); + } ++ ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.event.HoverEvent asHoverEvent(final ItemStack item, final java.util.function.UnaryOperator op) { ++ final net.minecraft.nbt.NBTTagCompound tag = CraftItemStack.asNMSCopy(item).getTag(); ++ return net.kyori.adventure.text.event.HoverEvent.showItem(op.apply(net.kyori.adventure.text.event.HoverEvent.ShowItem.of(item.getType().getKey(), item.getAmount(), io.papermc.paper.adventure.PaperAdventure.asBinaryTagHolder(tag)))); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +index ef10c7ab1d615cdba182eca63eb14309339a5314..206c133ebc6c44038585236b0628543b8bed278c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +@@ -14,10 +14,17 @@ import org.apache.commons.lang.Validate; + + public class CraftMerchantCustom extends CraftMerchant { + ++ @Deprecated // Paper - Adventure + public CraftMerchantCustom(String title) { + super(new MinecraftMerchant(title)); + getMerchant().craftMerchant = this; + } ++ // Paper start ++ public CraftMerchantCustom(net.kyori.adventure.text.Component title) { ++ super(new MinecraftMerchant(title)); ++ getMerchant().craftMerchant = this; ++ } ++ // Paper end + + @Override + public String toString() { +@@ -37,10 +44,17 @@ public class CraftMerchantCustom extends CraftMerchant { + private World tradingWorld; + protected CraftMerchant craftMerchant; + ++ @Deprecated // Paper - Adventure + public MinecraftMerchant(String title) { + Validate.notNull(title, "Title cannot be null"); + this.title = new ChatComponentText(title); + } ++ // Paper start ++ public MinecraftMerchant(net.kyori.adventure.text.Component title) { ++ Validate.notNull(title, "Title cannot be null"); ++ this.title = io.papermc.paper.adventure.PaperAdventure.asVanilla(title); ++ } ++ // Paper end + + @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 4cdc504df4cad6f7725f6d18482e88433523943a..af1f45bb8bbe380eec1dcdef1beacb06c9d932a8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +@@ -1,8 +1,9 @@ + package org.bukkit.craftbukkit.inventory; + + import com.google.common.collect.ImmutableList; +-import com.google.common.collect.ImmutableMap.Builder; + import com.google.common.collect.Lists; ++ ++import com.google.common.collect.ImmutableMap; // Paper + import java.util.ArrayList; + import java.util.Arrays; + import java.util.List; +@@ -269,6 +270,135 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + this.generation = (generation == null) ? null : generation.ordinal(); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component title() { ++ return this.title == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(this.title); ++ } ++ ++ @Override ++ public org.bukkit.inventory.meta.BookMeta title(net.kyori.adventure.text.Component title) { ++ this.setTitle(title == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(title)); ++ return this; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.Component author() { ++ return this.author == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(this.author); ++ } ++ ++ @Override ++ public org.bukkit.inventory.meta.BookMeta author(net.kyori.adventure.text.Component author) { ++ this.setAuthor(author == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(author)); ++ return this; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.Component page(final int page) { ++ Validate.isTrue(isValidPage(page), "Invalid page number"); ++ return this instanceof CraftMetaBookSigned ? net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(pages.get(page - 1)) : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(pages.get(page - 1)); ++ } ++ ++ @Override ++ public void page(final int page, net.kyori.adventure.text.Component data) { ++ if (!isValidPage(page)) { ++ throw new IllegalArgumentException("Invalid page number " + page + "/" + pages.size()); ++ } ++ if (data == null) { ++ data = net.kyori.adventure.text.Component.empty(); ++ } ++ pages.set(page - 1, this instanceof CraftMetaBookSigned ? net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(data) : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(data)); ++ } ++ ++ @Override ++ public List pages() { ++ if (this.pages == null) return ImmutableList.of(); ++ if (this instanceof CraftMetaBookSigned) ++ return pages.stream().map(net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson()::deserialize).collect(ImmutableList.toImmutableList()); ++ else ++ return pages.stream().map(io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC::deserialize).collect(ImmutableList.toImmutableList()); ++ } ++ ++ @Override ++ public BookMeta pages(List pages) { ++ if (this.pages != null) this.pages.clear(); ++ for (net.kyori.adventure.text.Component page : pages) { ++ addPages(page); ++ } ++ return this; ++ } ++ ++ @Override ++ public BookMeta pages(net.kyori.adventure.text.Component... pages) { ++ if (this.pages != null) this.pages.clear(); ++ addPages(pages); ++ return this; ++ } ++ ++ @Override ++ public void addPages(net.kyori.adventure.text.Component... pages) { ++ if (this.pages == null) this.pages = new ArrayList<>(); ++ for (net.kyori.adventure.text.Component page : pages) { ++ if (this.pages.size() >= MAX_PAGES) { ++ return; ++ } ++ ++ if (page == null) { ++ page = net.kyori.adventure.text.Component.empty(); ++ } ++ ++ this.pages.add(this instanceof CraftMetaBookSigned ? net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(page) : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(page)); ++ } ++ } ++ ++ private CraftMetaBook(net.kyori.adventure.text.Component title, net.kyori.adventure.text.Component author, List pages) { ++ super((org.bukkit.craftbukkit.inventory.CraftMetaItem) org.bukkit.Bukkit.getItemFactory().getItemMeta(org.bukkit.Material.WRITABLE_BOOK)); ++ this.title = title == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(title); ++ this.author = author == null ? null : io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(author); ++ this.pages = pages.subList(0, Math.min(MAX_PAGES, pages.size())).stream().map(io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC::serialize).collect(java.util.stream.Collectors.toList()); ++ } ++ ++ static final class CraftMetaBookBuilder implements BookMetaBuilder { ++ private net.kyori.adventure.text.Component title = null; ++ private net.kyori.adventure.text.Component author = null; ++ private final List pages = new java.util.ArrayList<>(); ++ ++ @Override ++ public BookMetaBuilder title(net.kyori.adventure.text.Component title) { ++ this.title = title; ++ return this; ++ } ++ ++ @Override ++ public BookMetaBuilder author(net.kyori.adventure.text.Component author) { ++ this.author = author; ++ return this; ++ } ++ ++ @Override ++ public BookMetaBuilder addPage(net.kyori.adventure.text.Component page) { ++ this.pages.add(page); ++ return this; ++ } ++ ++ @Override ++ public BookMetaBuilder pages(net.kyori.adventure.text.Component... pages) { ++ java.util.Collections.addAll(this.pages, pages); ++ return this; ++ } ++ ++ @Override ++ public BookMetaBuilder pages(java.util.Collection pages) { ++ this.pages.addAll(pages); ++ return this; ++ } ++ ++ @Override ++ public BookMeta build() { ++ return new CraftMetaBook(title, author, pages); ++ } ++ } ++ // Paper end + @Override + public String getPage(final int page) { + Validate.isTrue(isValidPage(page), "Invalid page number"); +@@ -413,7 +543,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + } + + @Override +- Builder serialize(Builder builder) { ++ ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { + super.serialize(builder); + + if (hasTitle()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java +index 0835541f51e643ae824c197be7100d5849b5e92a..0d58ec9834797ad7b9acaae6353dcf0385c53fd4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java +@@ -1,6 +1,6 @@ + package org.bukkit.craftbukkit.inventory; + +-import com.google.common.collect.ImmutableMap.Builder; ++import com.google.common.collect.ImmutableMap; // Paper + import java.util.Map; + import net.minecraft.nbt.NBTTagCompound; + import org.bukkit.Material; +@@ -84,7 +84,7 @@ class CraftMetaBookSigned extends CraftMetaBook implements BookMeta { + } + + @Override +- Builder serialize(Builder builder) { ++ ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { + super.serialize(builder); + return builder; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 928328c292a1322cab478bc748761baf8608e4b0..7a11b2ddfa4244459253c918315aaab78ef2eb4a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -745,6 +745,18 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || (lore != null) || hasCustomModelData() || hasBlockData() || hasRepairCost() || !unhandledTags.isEmpty() || !persistentDataContainer.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers()); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component displayName() { ++ return displayName == null ? null : net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(displayName); ++ } ++ ++ @Override ++ public void displayName(final net.kyori.adventure.text.Component displayName) { ++ this.displayName = displayName == null ? null : net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(displayName); ++ } ++ // Paper end ++ + @Override + public String getDisplayName() { + return CraftChatMessage.fromJSONComponent(displayName); +@@ -780,6 +792,18 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return this.lore != null && !this.lore.isEmpty(); + } + ++ // Paper start ++ @Override ++ public List lore() { ++ return this.lore != null ? io.papermc.paper.adventure.PaperAdventure.asAdventureFromJson(this.lore) : null; ++ } ++ ++ @Override ++ public void lore(final List lore) { ++ this.lore = lore != null ? io.papermc.paper.adventure.PaperAdventure.asJson(lore) : null; ++ } ++ // Paper end ++ + @Override + public boolean hasRepairCost() { + return repairCost > 0; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftCustomInventoryConverter.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftCustomInventoryConverter.java +index ed4415f6dd588c08c922efd5beebb3b124beb9d6..78a7ac47f20e84ccd67ff44d0bc7a2f2faa0d476 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftCustomInventoryConverter.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftCustomInventoryConverter.java +@@ -12,6 +12,13 @@ public class CraftCustomInventoryConverter implements CraftInventoryCreator.Inve + return new CraftInventoryCustom(holder, type); + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ return new CraftInventoryCustom(owner, type, title); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) { + return new CraftInventoryCustom(owner, type, title); +@@ -21,6 +28,12 @@ public class CraftCustomInventoryConverter implements CraftInventoryCreator.Inve + return new CraftInventoryCustom(owner, size); + } + ++ // Paper start ++ public Inventory createInventory(InventoryHolder owner, int size, net.kyori.adventure.text.Component title) { ++ return new CraftInventoryCustom(owner, size, title); ++ } ++ // Paper end ++ + public Inventory createInventory(InventoryHolder owner, int size, String title) { + return new CraftInventoryCustom(owner, size, title); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java +index b51321c8dd70a90ab149f456c7ffb4587c4fbd34..94d807c5d09f165c6eedd0a1c4026c2b833806a0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java +@@ -43,6 +43,17 @@ public final class CraftInventoryCreator { + return converterMap.get(type).createInventory(holder, type); + } + ++ // Paper start ++ public Inventory createInventory(InventoryHolder holder, InventoryType type, net.kyori.adventure.text.Component title) { ++ // Paper start ++ if (holder != null) { ++ return DEFAULT_CONVERTER.createInventory(holder, type, title); ++ } ++ //noinspection ConstantConditions // Paper end ++ return converterMap.get(type).createInventory(holder, type, title); ++ } ++ // Paper end ++ + public Inventory createInventory(InventoryHolder holder, InventoryType type, String title) { + return converterMap.get(type).createInventory(holder, type, title); + } +@@ -51,6 +62,12 @@ public final class CraftInventoryCreator { + return DEFAULT_CONVERTER.createInventory(holder, size); + } + ++ // Paper start ++ public Inventory createInventory(InventoryHolder holder, int size, net.kyori.adventure.text.Component title) { ++ return DEFAULT_CONVERTER.createInventory(holder, size, title); ++ } ++ // Paper end ++ + public Inventory createInventory(InventoryHolder holder, int size, String title) { + return DEFAULT_CONVERTER.createInventory(holder, size, title); + } +@@ -59,6 +76,10 @@ public final class CraftInventoryCreator { + + Inventory createInventory(InventoryHolder holder, InventoryType type); + ++ // Paper start ++ Inventory createInventory(InventoryHolder holder, InventoryType type, net.kyori.adventure.text.Component title); ++ // Paper end ++ + Inventory createInventory(InventoryHolder holder, InventoryType type, String title); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java +index f35e66dab9ff63ca05d7e303c71106c0e9971309..2bd4e644ffbde2e1133b25824a2829bc6b33fa84 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java +@@ -31,6 +31,18 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat + return getInventory(getTileEntity()); + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ IInventory te = getTileEntity(); ++ if (te instanceof TileEntityLootable) { ++ ((TileEntityLootable) te).setCustomName(io.papermc.paper.adventure.PaperAdventure.asVanilla(title)); ++ } ++ ++ return getInventory(te); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder holder, InventoryType type, String title) { + IInventory te = getTileEntity(); +@@ -54,6 +66,15 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat + return furnace; + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ IInventory tileEntity = getTileEntity(); ++ ((TileEntityFurnace) tileEntity).setCustomName(io.papermc.paper.adventure.PaperAdventure.asVanilla(title)); ++ return getInventory(tileEntity); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) { + IInventory tileEntity = getTileEntity(); +@@ -74,6 +95,18 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat + return new TileEntityBrewingStand(); + } + ++ // Paper start ++ @Override ++ public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { ++ // BrewingStand does not extend TileEntityLootable ++ IInventory tileEntity = getTileEntity(); ++ if (tileEntity instanceof TileEntityBrewingStand) { ++ ((TileEntityBrewingStand) tileEntity).setCustomName(io.papermc.paper.adventure.PaperAdventure.asVanilla(title)); ++ } ++ return getInventory(tileEntity); ++ } ++ // Paper end ++ + @Override + public Inventory createInventory(InventoryHolder holder, InventoryType type, String title) { + // BrewingStand does not extend TileEntityLootable +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java +index 8fedca656af0783f3d97a7ccde3a113f97911084..df3deee12b11508b76c5f8f927fac8db54a7e397 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java +@@ -31,6 +31,21 @@ final class CraftObjective extends CraftScoreboardComponent implements Objective + return objective.getName(); + } + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component displayName() throws IllegalStateException { ++ CraftScoreboard scoreboard = checkState(); ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(objective.getDisplayName()); ++ } ++ @Override ++ public void displayName(net.kyori.adventure.text.Component displayName) throws IllegalStateException, IllegalArgumentException { ++ if (displayName == null) { ++ displayName = net.kyori.adventure.text.Component.empty(); ++ } ++ CraftScoreboard scoreboard = checkState(); ++ objective.setDisplayName(io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName)); ++ } ++ // Paper end + @Override + public String getDisplayName() throws IllegalStateException { + CraftScoreboard scoreboard = checkState(); +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +index 954389b818de93cf0ab046edc5dc032fceea391b..6ea491f6308317059c4bc6735abbdce370df0f34 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +@@ -28,6 +28,27 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + public CraftObjective registerNewObjective(String name, String criteria) throws IllegalArgumentException { + return registerNewObjective(name, criteria, name); + } ++ // Paper start ++ @Override ++ public CraftObjective registerNewObjective(String name, String criteria, net.kyori.adventure.text.Component displayName) { ++ return registerNewObjective(name, criteria, displayName, org.bukkit.scoreboard.RenderType.INTEGER); ++ } ++ @Override ++ public CraftObjective registerNewObjective(String name, String criteria, net.kyori.adventure.text.Component displayName, RenderType renderType) { ++ if (displayName == null) { ++ displayName = net.kyori.adventure.text.Component.empty(); ++ } ++ Validate.notNull(name, "Objective name cannot be null"); ++ Validate.notNull(criteria, "Criteria cannot be null"); ++ Validate.notNull(displayName, "Display name cannot be null"); ++ Validate.notNull(renderType, "RenderType cannot be null"); ++ Validate.isTrue(name.length() <= 16, "The name '" + name + "' is longer than the limit of 16 characters"); ++ Validate.isTrue(board.getObjective(name) == null, "An objective of name '" + name + "' already exists"); ++ CraftCriteria craftCriteria = CraftCriteria.getFromBukkit(criteria); ++ ScoreboardObjective objective = board.registerObjective(name, craftCriteria.criteria, io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName), CraftScoreboardTranslations.fromBukkitRender(renderType)); ++ return new CraftObjective(this, objective); ++ } ++ // Paper end + + @Override + public CraftObjective registerNewObjective(String name, String criteria, String displayName) throws IllegalArgumentException { +@@ -36,7 +57,7 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + + @Override + public CraftObjective registerNewObjective(String name, String criteria, String displayName, RenderType renderType) throws IllegalArgumentException { +- Validate.notNull(name, "Objective name cannot be null"); ++ /*Validate.notNull(name, "Objective name cannot be null"); // Paper + Validate.notNull(criteria, "Criteria cannot be null"); + Validate.notNull(displayName, "Display name cannot be null"); + Validate.notNull(renderType, "RenderType cannot be null"); +@@ -46,7 +67,8 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + + CraftCriteria craftCriteria = CraftCriteria.getFromBukkit(criteria); + ScoreboardObjective objective = board.registerObjective(name, craftCriteria.criteria, CraftChatMessage.fromStringOrNull(displayName), CraftScoreboardTranslations.fromBukkitRender(renderType)); +- return new CraftObjective(this, objective); ++ return new CraftObjective(this, objective);*/ // Paper ++ return registerNewObjective(name, criteria, io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(displayName), renderType); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +index a213c2e3b2680c6d1bd38853580cbdb52ae7779e..c631934fe9d205a06956c900d5b58a1d8a781c19 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +@@ -29,6 +29,55 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { + + return team.getName(); + } ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component displayName() throws IllegalStateException { ++ CraftScoreboard scoreboard = checkState(); ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(team.getDisplayName()); ++ } ++ @Override ++ public void displayName(net.kyori.adventure.text.Component displayName) throws IllegalStateException, IllegalArgumentException { ++ if (displayName == null) displayName = net.kyori.adventure.text.Component.empty(); ++ CraftScoreboard scoreboard = checkState(); ++ team.setDisplayName(io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName)); ++ } ++ @Override ++ public net.kyori.adventure.text.Component prefix() throws IllegalStateException { ++ CraftScoreboard scoreboard = checkState(); ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(team.getPrefix()); ++ } ++ @Override ++ public void prefix(net.kyori.adventure.text.Component prefix) throws IllegalStateException, IllegalArgumentException { ++ if (prefix == null) prefix = net.kyori.adventure.text.Component.empty(); ++ CraftScoreboard scoreboard = checkState(); ++ team.setPrefix(io.papermc.paper.adventure.PaperAdventure.asVanilla(prefix)); ++ } ++ @Override ++ public net.kyori.adventure.text.Component suffix() throws IllegalStateException { ++ CraftScoreboard scoreboard = checkState(); ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(team.getSuffix()); ++ } ++ @Override ++ public void suffix(net.kyori.adventure.text.Component suffix) throws IllegalStateException, IllegalArgumentException { ++ if (suffix == null) suffix = net.kyori.adventure.text.Component.empty(); ++ CraftScoreboard scoreboard = checkState(); ++ team.setSuffix(io.papermc.paper.adventure.PaperAdventure.asVanilla(suffix)); ++ } ++ @Override ++ public net.kyori.adventure.text.format.TextColor color() throws IllegalStateException { ++ CraftScoreboard scoreboard = checkState(); ++ if (team.getColor().getHexValue() == null) throw new IllegalStateException("Team colors must have hex values"); ++ net.kyori.adventure.text.format.TextColor color = net.kyori.adventure.text.format.TextColor.color(team.getColor().getHexValue()); ++ if (!(color instanceof net.kyori.adventure.text.format.NamedTextColor)) throw new IllegalStateException("Team doesn't have a NamedTextColor"); ++ return (net.kyori.adventure.text.format.NamedTextColor) color; ++ } ++ @Override ++ public void color(net.kyori.adventure.text.format.NamedTextColor color) { ++ if (color == null) color = net.kyori.adventure.text.format.NamedTextColor.WHITE; ++ CraftScoreboard scoreboard = checkState(); ++ team.setColor(io.papermc.paper.adventure.PaperAdventure.asVanilla(color)); ++ } ++ // Paper end + + @Override + public String getDisplayName() throws IllegalStateException { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +index 6a0b4cd36ac54df41642e8499c50e59f2b347b48..666af6cc91bd12ba5d5a846d663a5aabf861fbc4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +@@ -290,6 +290,7 @@ public final class CraftChatMessage { + + public static String fromComponent(IChatBaseComponent component) { + if (component == null) return ""; ++ if (component instanceof io.papermc.paper.adventure.AdventureComponent) component = ((io.papermc.paper.adventure.AdventureComponent) component).deepConverted(); + StringBuilder out = new StringBuilder(); + + 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 65131f0977fa55c4761c34ce52720170feb61a72..8f737f63f280c00c1276bd1dc3ecf60448732ca8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -59,6 +59,33 @@ public final class CraftMagicNumbers implements UnsafeValues { + + private CraftMagicNumbers() {} + ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.flattener.ComponentFlattener componentFlattener() { ++ return io.papermc.paper.adventure.PaperAdventure.FLATTENER; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.serializer.gson.GsonComponentSerializer colorDownsamplingGsonComponentSerializer() { ++ return io.papermc.paper.adventure.PaperAdventure.COLOR_DOWNSAMPLING_GSON; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.serializer.gson.GsonComponentSerializer gsonComponentSerializer() { ++ return io.papermc.paper.adventure.PaperAdventure.GSON; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.serializer.plain.PlainComponentSerializer plainComponentSerializer() { ++ return io.papermc.paper.adventure.PaperAdventure.PLAIN; ++ } ++ ++ @Override ++ public net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer legacyComponentSerializer() { ++ return io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC; ++ } ++ // Paper end ++ + public static IBlockData getBlock(MaterialData material) { + return getBlock(material.getItemType(), material.getData()); + } diff --git a/patches/server-unmapped/0001/0012-Configurable-cactus-bamboo-and-reed-growth-heights.patch b/patches/server-unmapped/0001/0012-Configurable-cactus-bamboo-and-reed-growth-heights.patch new file mode 100644 index 0000000000..d184572c6b --- /dev/null +++ b/patches/server-unmapped/0001/0012-Configurable-cactus-bamboo-and-reed-growth-heights.patch @@ -0,0 +1,114 @@ +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 heights + +Bamboo - Both the minimum fully-grown heights and the maximum are configurable +- Machine_Maker + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index b31109d2dadd29e8852468c19265066b773d2be0..3618cc017feb60e257a28f67cbddca3f792a9833 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -65,4 +65,17 @@ public class PaperWorldConfig { + config.addDefault("world-settings.default." + path, def); + return config.getString("world-settings." + worldName + "." + path, config.getString("world-settings.default." + path)); + } ++ ++ public int cactusMaxHeight; ++ public int reedMaxHeight; ++ public int bambooMaxHeight; ++ public int bambooMinHeight; ++ private void blockGrowthHeight() { ++ cactusMaxHeight = getInt("max-growth-height.cactus", 3); ++ reedMaxHeight = getInt("max-growth-height.reeds", 3); ++ bambooMaxHeight = getInt("max-growth-height.bamboo.max", 16); ++ bambooMinHeight = getInt("max-growth-height.bamboo.min", 11); ++ log("Max height for cactus growth " + cactusMaxHeight + ". Max height for reed growth " + reedMaxHeight + ". Max height for bamboo growth " + bambooMaxHeight + ". Min height for fully-grown bamboo " + bambooMinHeight + "."); ++ ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/BlockBamboo.java b/src/main/java/net/minecraft/world/level/block/BlockBamboo.java +index 6d10a7cb48627f37dc91b73064edc5f18c2b5ce9..cd132eff3f82349518555d1d5a16778ca08a521b 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockBamboo.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockBamboo.java +@@ -124,7 +124,7 @@ public class BlockBamboo extends Block implements IBlockFragilePlantElement { + if (random.nextInt(Math.max(1, (int) (100.0F / worldserver.spigotConfig.bambooModifier) * 3)) == 0 && worldserver.isEmpty(blockposition.up()) && worldserver.getLightLevel(blockposition.up(), 0) >= 9) { // Spigot + int i = this.b(worldserver, blockposition) + 1; + +- if (i < 16) { ++ if (i < worldserver.paperConfig.bambooMaxHeight) { // Paper + this.a(iblockdata, (World) worldserver, blockposition, random, i); + } + } +@@ -155,7 +155,7 @@ public class BlockBamboo extends Block implements IBlockFragilePlantElement { + int i = this.a(iblockaccess, blockposition); + int j = this.b(iblockaccess, blockposition); + +- return i + j + 1 < 16 && (Integer) iblockaccess.getType(blockposition.up(i)).get(BlockBamboo.f) != 1; ++ return i + j + 1 < ((World) iblockaccess).paperConfig.bambooMaxHeight && (Integer) iblockaccess.getType(blockposition.up(i)).get(BlockBamboo.f) != 1; // Paper + } + + @Override +@@ -174,7 +174,7 @@ public class BlockBamboo extends Block implements IBlockFragilePlantElement { + BlockPosition blockposition1 = blockposition.up(i); + IBlockData iblockdata1 = worldserver.getType(blockposition1); + +- if (k >= 16 || !iblockdata1.a(Blocks.BAMBOO) || (Integer) iblockdata1.get(BlockBamboo.f) == 1 || !worldserver.isEmpty(blockposition1.up())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here ++ if (k >= worldserver.paperConfig.bambooMaxHeight || !iblockdata1.a(Blocks.BAMBOO) || (Integer) iblockdata1.get(BlockBamboo.f) == 1 || !worldserver.isEmpty(blockposition1.up())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here // Paper - Configurable cactus bamboo and reed growth heights + return; + } + +@@ -215,7 +215,7 @@ public class BlockBamboo extends Block implements IBlockFragilePlantElement { + } + + int j = (Integer) iblockdata.get(BlockBamboo.d) != 1 && !iblockdata2.a(Blocks.BAMBOO) ? 0 : 1; +- int k = (i < 11 || random.nextFloat() >= 0.25F) && i != 15 ? 0 : 1; ++ int k = (i < world.paperConfig.bambooMinHeight || random.nextFloat() >= 0.25F) && i != (world.paperConfig.bambooMaxHeight - 1) ? 0 : 1; // Paper + + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, blockposition, blockposition.up(), (IBlockData) ((IBlockData) ((IBlockData) this.getBlockData().set(BlockBamboo.d, j)).set(BlockBamboo.e, blockpropertybamboosize)).set(BlockBamboo.f, k), 3)) { +@@ -230,7 +230,7 @@ public class BlockBamboo extends Block implements IBlockFragilePlantElement { + protected int a(IBlockAccess iblockaccess, BlockPosition blockposition) { + int i; + +- for (i = 0; i < 16 && iblockaccess.getType(blockposition.up(i + 1)).a(Blocks.BAMBOO); ++i) { ++ for (i = 0; i < ((World) iblockaccess).paperConfig.bambooMaxHeight && iblockaccess.getType(blockposition.up(i + 1)).a(Blocks.BAMBOO); ++i) { // Paper + ; + } + +@@ -240,7 +240,7 @@ public class BlockBamboo extends Block implements IBlockFragilePlantElement { + protected int b(IBlockAccess iblockaccess, BlockPosition blockposition) { + int i; + +- for (i = 0; i < 16 && iblockaccess.getType(blockposition.down(i + 1)).a(Blocks.BAMBOO); ++i) { ++ for (i = 0; i < ((World) iblockaccess).paperConfig.bambooMaxHeight && iblockaccess.getType(blockposition.down(i + 1)).a(Blocks.BAMBOO); ++i) { // Paper + ; + } + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockCactus.java b/src/main/java/net/minecraft/world/level/block/BlockCactus.java +index c2316e346f9b647edae4c9709defa4531e04eaa6..9f1e9fc5361cd051b909e2e1b2095722064185da 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockCactus.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockCactus.java +@@ -55,7 +55,7 @@ public class BlockCactus extends Block { + ; + } + +- if (i < 3) { ++ if (i < worldserver.paperConfig.cactusMaxHeight) { // Paper - Configurable growth height + int j = (Integer) iblockdata.get(BlockCactus.AGE); + + if (j >= (byte) range(3, ((100.0F / worldserver.spigotConfig.cactusModifier) * 15) + 0.5F, 15)) { // Spigot +diff --git a/src/main/java/net/minecraft/world/level/block/BlockReed.java b/src/main/java/net/minecraft/world/level/block/BlockReed.java +index eb95d65e1a37b91a71a9ad8968710ba9047f3980..a4ede7968ba0134f0d2cf880a6b4a6b9a81f2fcd 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockReed.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockReed.java +@@ -51,7 +51,7 @@ public class BlockReed extends Block { + ; + } + +- if (i < 3) { ++ if (i < worldserver.paperConfig.reedMaxHeight) { // Paper - Configurable growth height + int j = (Integer) iblockdata.get(BlockReed.AGE); + + if (j >= (byte) range(3, ((100.0F / worldserver.spigotConfig.caneModifier) * 15) + 0.5F, 15)) { // Spigot diff --git a/patches/server-unmapped/0001/0013-Configurable-baby-zombie-movement-speed.patch b/patches/server-unmapped/0001/0013-Configurable-baby-zombie-movement-speed.patch new file mode 100644 index 0000000000..060f23376f --- /dev/null +++ b/patches/server-unmapped/0001/0013-Configurable-baby-zombie-movement-speed.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 13:09:16 -0600 +Subject: [PATCH] Configurable baby zombie movement speed + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3618cc017feb60e257a28f67cbddca3f792a9833..796c17e0941922a9716212c6eae91643d8360418 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -78,4 +78,15 @@ public class PaperWorldConfig { + log("Max height for cactus growth " + cactusMaxHeight + ". Max height for reed growth " + reedMaxHeight + ". Max height for bamboo growth " + bambooMaxHeight + ". Min height for fully-grown bamboo " + bambooMinHeight + "."); + + } ++ ++ public double babyZombieMovementModifier; ++ private void babyZombieMovementModifier() { ++ babyZombieMovementModifier = getDouble("baby-zombie-movement-modifier", 0.5D); ++ if (PaperConfig.version < 20) { ++ babyZombieMovementModifier = getDouble("baby-zombie-movement-speed", 0.5D); ++ set("baby-zombie-movement-modifier", babyZombieMovementModifier); ++ } ++ ++ log("Baby zombies will move at the speed of " + babyZombieMovementModifier); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +index daa6d1ca20f952971a2ad6a0c4cba0bef6e43bf6..219e3b1626d68ede57b08a706d24bb6bc4b13fac 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +@@ -82,7 +82,7 @@ import org.bukkit.event.entity.EntityTransformEvent; + public class EntityZombie extends EntityMonster { + + private static final UUID b = UUID.fromString("B9766B59-9566-4402-BC1F-2EE2A276D836"); +- private static final AttributeModifier c = new AttributeModifier(EntityZombie.b, "Baby speed boost", 0.5D, AttributeModifier.Operation.MULTIPLY_BASE); ++ private final AttributeModifier c = new AttributeModifier(EntityZombie.b, "Baby speed boost", 0.5D, AttributeModifier.Operation.MULTIPLY_BASE); private final AttributeModifier babyModifier = this.c; // Paper - remove static - Make baby speed configurable + private static final DataWatcherObject d = DataWatcher.a(EntityZombie.class, DataWatcherRegistry.i); + private static final DataWatcherObject bo = DataWatcher.a(EntityZombie.class, DataWatcherRegistry.b); + public static final DataWatcherObject DROWN_CONVERTING = DataWatcher.a(EntityZombie.class, DataWatcherRegistry.i); +@@ -185,9 +185,9 @@ public class EntityZombie extends EntityMonster { + if (this.world != null && !this.world.isClientSide) { + AttributeModifiable attributemodifiable = this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED); + +- attributemodifiable.removeModifier(EntityZombie.c); ++ attributemodifiable.removeModifier(this.babyModifier); // Paper + if (flag) { +- attributemodifiable.b(EntityZombie.c); ++ attributemodifiable.b(this.babyModifier); // Paper + } + } + diff --git a/patches/server-unmapped/0001/0014-Configurable-fishing-time-ranges.patch b/patches/server-unmapped/0001/0014-Configurable-fishing-time-ranges.patch new file mode 100644 index 0000000000..7d7162311a --- /dev/null +++ b/patches/server-unmapped/0001/0014-Configurable-fishing-time-ranges.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 13:14:11 -0600 +Subject: [PATCH] Configurable fishing time ranges + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 796c17e0941922a9716212c6eae91643d8360418..78948c42b13194005bdbbbc69c2b7ae0732a78c5 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -89,4 +89,12 @@ public class PaperWorldConfig { + + log("Baby zombies will move at the speed of " + babyZombieMovementModifier); + } ++ ++ public int fishingMinTicks; ++ public int fishingMaxTicks; ++ private void fishingTickRange() { ++ fishingMinTicks = getInt("fishing-time-range.MinimumTicks", 100); ++ fishingMaxTicks = getInt("fishing-time-range.MaximumTicks", 600); ++ log("Fishing time ranges are between " + fishingMinTicks +" and " + fishingMaxTicks + " ticks"); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java +index 2b75eab985b0f39e1624e5f4cab7e5b5bde6ae14..bcc411107d531529dbce9d1d43896a3c70e63012 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java +@@ -85,6 +85,10 @@ public class EntityFishingHook extends IProjectile { + entityhuman.hookedFish = this; + this.an = Math.max(0, i); + this.lureLevel = Math.max(0, j); ++ // Paper start ++ minWaitTime = world.paperConfig.fishingMinTicks; ++ maxWaitTime = world.paperConfig.fishingMaxTicks; ++ // paper end + } + + public EntityFishingHook(EntityHuman entityhuman, World world, int i, int j) { diff --git a/patches/server-unmapped/0001/0015-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch b/patches/server-unmapped/0001/0015-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch new file mode 100644 index 0000000000..3c4047985e --- /dev/null +++ b/patches/server-unmapped/0001/0015-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch @@ -0,0 +1,98 @@ +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 and take water damage + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 78948c42b13194005bdbbbc69c2b7ae0732a78c5..b41e7922dd96c3358eb849ab39982a75736e3476 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -97,4 +97,9 @@ public class PaperWorldConfig { + fishingMaxTicks = getInt("fishing-time-range.MaximumTicks", 600); + log("Fishing time ranges are between " + fishingMinTicks +" and " + fishingMaxTicks + " ticks"); + } ++ ++ public boolean nerfedMobsShouldJump; ++ private void nerfedMobsShouldJump() { ++ nerfedMobsShouldJump = getBoolean("spawner-nerfed-mobs-should-jump", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 306f6c0db2333cce5dfc4bf1c09bfef05119a28b..f823763a2f7f40d0be8d058a1bd61386bcd951e6 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1105,6 +1105,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return this.isInWater() || this.isInRain(); + } + ++ public final boolean isInWaterOrRainOrBubble() { return aG(); } // Paper - OBFHELPER + public boolean aG() { + return this.isInWater() || this.isInRain() || this.k(); + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +index dbfcdc3cc7c1dccf785f5e13634e84c5af088985..28aa0a9361e8a32763d7fe1af060f0016e8c1e50 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java ++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +@@ -98,6 +98,7 @@ public abstract class EntityInsentient extends EntityLiving { + private final EntityAIBodyControl c; + protected NavigationAbstract navigation; + public PathfinderGoalSelector goalSelector; ++ @Nullable public PathfinderGoalFloat goalFloat; // Paper + public PathfinderGoalSelector targetSelector; + private EntityLiving goalTarget; + private final EntitySenses bo; +@@ -784,7 +785,17 @@ public abstract class EntityInsentient extends EntityLiving { + @Override + protected final void doTick() { + ++this.ticksFarFromPlayer; +- if (!this.aware) return; // CraftBukkit ++ if (!this.aware) { // Paper start - Allow nerfed mobs to jump, float and take water damage ++ if (goalFloat != null) { ++ if (goalFloat.validConditions()) goalFloat.update(); ++ this.getControllerJump().jumpIfSet(); ++ } ++ if ((this instanceof EntityBlaze || this instanceof EntityEnderman) && isInWaterOrRainOrBubble()) { ++ damageEntity(DamageSource.DROWN, 1.0F); ++ } ++ return; ++ } ++ // Paper end + this.world.getMethodProfiler().enter("sensing"); + this.bo.a(); + this.world.getMethodProfiler().exit(); +diff --git a/src/main/java/net/minecraft/world/entity/ai/control/ControllerJump.java b/src/main/java/net/minecraft/world/entity/ai/control/ControllerJump.java +index 9767ac416fcd60a8a57b648dcb3f1e427bacd54d..1a9b3e0e0c090683e332dfa53708f8a62c8f14e0 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/control/ControllerJump.java ++++ b/src/main/java/net/minecraft/world/entity/ai/control/ControllerJump.java +@@ -15,6 +15,7 @@ public class ControllerJump { + this.a = true; + } + ++ public final void jumpIfSet() { this.b(); } // Paper - OBFHELPER + public void b() { + this.b.setJumping(this.a); + this.a = false; +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalFloat.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalFloat.java +index 8dfa1a6ade7f51e5d68b290f5376d999bb4c60ab..a6c8763139ed18fe73b2d6f6ec511e59666dc843 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalFloat.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalFloat.java +@@ -11,15 +11,18 @@ public class PathfinderGoalFloat extends PathfinderGoal { + + public PathfinderGoalFloat(EntityInsentient entityinsentient) { + this.a = entityinsentient; ++ if (entityinsentient.getWorld().paperConfig.nerfedMobsShouldJump) entityinsentient.goalFloat = this; // Paper + this.a(EnumSet.of(PathfinderGoal.Type.JUMP)); + entityinsentient.getNavigation().d(true); + } + ++ public final boolean validConditions() { return this.a(); } // Paper - OBFHELPER + @Override + public boolean a() { + return this.a.isInWater() && this.a.b((Tag) TagsFluid.WATER) > this.a.cx() || this.a.aQ(); + } + ++ public void update() { this.e(); } // Paper - OBFHELPER + @Override + public void e() { + if (this.a.getRandom().nextFloat() < 0.8F) { diff --git a/patches/server-unmapped/0001/0016-Add-configurable-despawn-distances-for-living-entiti.patch b/patches/server-unmapped/0001/0016-Add-configurable-despawn-distances-for-living-entiti.patch new file mode 100644 index 0000000000..edb9677fb5 --- /dev/null +++ b/patches/server-unmapped/0001/0016-Add-configurable-despawn-distances-for-living-entiti.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Suddenly +Date: Tue, 1 Mar 2016 13:51:54 -0600 +Subject: [PATCH] Add configurable despawn distances for living entities + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index b41e7922dd96c3358eb849ab39982a75736e3476..2f0d582baf0eb2bb477944d0cb1369db6ca33956 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -102,4 +102,20 @@ public class PaperWorldConfig { + private void nerfedMobsShouldJump() { + nerfedMobsShouldJump = getBoolean("spawner-nerfed-mobs-should-jump", false); + } ++ ++ public int softDespawnDistance; ++ public int hardDespawnDistance; ++ private void despawnDistances() { ++ softDespawnDistance = getInt("despawn-ranges.soft", 32); // 32^2 = 1024, Minecraft Default ++ hardDespawnDistance = getInt("despawn-ranges.hard", 128); // 128^2 = 16384, Minecraft Default ++ ++ if (softDespawnDistance > hardDespawnDistance) { ++ softDespawnDistance = hardDespawnDistance; ++ } ++ ++ log("Living Entity Despawn Ranges: Soft: " + softDespawnDistance + " Hard: " + hardDespawnDistance); ++ ++ softDespawnDistance = softDespawnDistance*softDespawnDistance; ++ hardDespawnDistance = hardDespawnDistance*hardDespawnDistance; ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +index 28aa0a9361e8a32763d7fe1af060f0016e8c1e50..f93af56f68d5fd27eca38d333ca429ce22fc397b 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java ++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +@@ -763,14 +763,14 @@ public abstract class EntityInsentient extends EntityLiving { + int i = this.getEntityType().e().f(); + int j = i * i; + +- if (d0 > (double) j) { // CraftBukkit - remove isTypeNotPersistent() check ++ if (d0 > (double) world.paperConfig.hardDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances + this.die(); + } + + int k = this.getEntityType().e().g(); + int l = k * k; + +- if (this.ticksFarFromPlayer > 600 && this.random.nextInt(800) == 0 && d0 > (double) l) { // CraftBukkit - remove isTypeNotPersistent() check ++ if (this.ticksFarFromPlayer > 600 && this.random.nextInt(800) == 0 && d0 > world.paperConfig.softDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances + this.die(); + } else if (d0 < (double) l) { + this.ticksFarFromPlayer = 0; diff --git a/patches/server-unmapped/0001/0017-Allow-for-toggling-of-spawn-chunks.patch b/patches/server-unmapped/0001/0017-Allow-for-toggling-of-spawn-chunks.patch new file mode 100644 index 0000000000..2aa51ee962 --- /dev/null +++ b/patches/server-unmapped/0001/0017-Allow-for-toggling-of-spawn-chunks.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 3 Mar 2016 03:53:43 -0600 +Subject: [PATCH] Allow for toggling of spawn chunks + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 2f0d582baf0eb2bb477944d0cb1369db6ca33956..89e76dd73811fd0f6f8c8e7e5af804d5a4bb5a75 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -118,4 +118,10 @@ public class PaperWorldConfig { + softDespawnDistance = softDespawnDistance*softDespawnDistance; + hardDespawnDistance = hardDespawnDistance*hardDespawnDistance; + } ++ ++ public boolean keepSpawnInMemory; ++ private void keepSpawnInMemory() { ++ keepSpawnInMemory = getBoolean("keep-spawn-loaded", true); ++ log("Keep spawn chunk loaded: " + keepSpawnInMemory); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 0f592e3c67f5b0e9c8035af6e2c34c03e2ed2e4c..d2848c8b03ca2ffa2d164fedb11ee22746bbf10b 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -217,6 +217,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + }); + // CraftBukkit end + timings = new co.aikar.timings.WorldTimingsHandler(this); // Paper - code below can generate new world and access timings ++ this.keepSpawnInMemory = this.paperConfig.keepSpawnInMemory; // Paper + this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); + } diff --git a/patches/server-unmapped/0001/0018-Drop-falling-block-and-tnt-entities-at-the-specified.patch b/patches/server-unmapped/0001/0018-Drop-falling-block-and-tnt-entities-at-the-specified.patch new file mode 100644 index 0000000000..ec9ba5059c --- /dev/null +++ b/patches/server-unmapped/0001/0018-Drop-falling-block-and-tnt-entities-at-the-specified.patch @@ -0,0 +1,94 @@ +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 + +* Dec 2, 2020 Added tnt nerf for tnt minecarts - Machine_Maker + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 89e76dd73811fd0f6f8c8e7e5af804d5a4bb5a75..d16ae924bcbe31c964f7fb448757c748e5c4418c 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -124,4 +124,14 @@ public class PaperWorldConfig { + keepSpawnInMemory = getBoolean("keep-spawn-loaded", true); + log("Keep spawn chunk loaded: " + keepSpawnInMemory); + } ++ ++ public int fallingBlockHeightNerf; ++ public int entityTNTHeightNerf; ++ private void heightNerfs() { ++ fallingBlockHeightNerf = getInt("falling-block-height-nerf", 0); ++ entityTNTHeightNerf = getInt("tnt-entity-height-nerf", 0); ++ ++ if (fallingBlockHeightNerf != 0) log("Falling Block Height Limit set to Y: " + fallingBlockHeightNerf); ++ if (entityTNTHeightNerf != 0) log("TNT Entity Height Limit set to Y: " + entityTNTHeightNerf); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f823763a2f7f40d0be8d058a1bd61386bcd951e6..20e4ff812960a54872f2fea8fe6baf7bb1ef077d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1850,6 +1850,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return this.a(itemstack, 0.0F); + } + ++ @Nullable public final EntityItem dropItem(ItemStack itemstack, float offset) { return this.a(itemstack, offset); } // Paper - OBFHELPER + @Nullable + public EntityItem a(ItemStack itemstack, float f) { + if (itemstack.isEmpty()) { +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java b/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java +index b3dea8d65c3d1e27e59be142ec9e2ebb4164aed0..901522f24b8bc58861e46eda400dbab92bb6401d 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java +@@ -124,6 +124,17 @@ public class EntityFallingBlock extends Entity { + } + + this.move(EnumMoveType.SELF, this.getMot()); ++ ++ // Paper start - Configurable EntityFallingBlock height nerf ++ if (this.world.paperConfig.fallingBlockHeightNerf != 0 && this.locY() > this.world.paperConfig.fallingBlockHeightNerf) { ++ if (this.dropItem && this.world.getGameRules().getBoolean(GameRules.DO_ENTITY_DROPS)) { ++ this.a(block); ++ } ++ ++ this.die(); ++ return; ++ } ++ // Paper end + if (!this.world.isClientSide) { + blockposition = this.getChunkCoordinates(); + boolean flag = this.block.getBlock() instanceof BlockConcretePowder; +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java b/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java +index c2e7161dc103c971908ff217eaf972e9f175d044..4f4b2b8d58223fa22d6a7af5c94cfb36399b9641 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java +@@ -70,6 +70,12 @@ public class EntityTNTPrimed extends Entity { + } + + this.move(EnumMoveType.SELF, this.getMot()); ++ // Paper start - Configurable TNT entity height nerf ++ if (this.world.paperConfig.entityTNTHeightNerf != 0 && this.locY() > this.world.paperConfig.entityTNTHeightNerf) { ++ this.die(); ++ return; ++ } ++ // Paper end + this.setMot(this.getMot().a(0.98D)); + if (this.onGround) { + this.setMot(this.getMot().d(0.7D, -0.5D, 0.7D)); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartTNT.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartTNT.java +index ee31704ffb88ab68702657554d386e8ebfa05d03..4bec5c1d504b9456dafe1b76bdbb523d0a324abe 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartTNT.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartTNT.java +@@ -47,6 +47,12 @@ public class EntityMinecartTNT extends EntityMinecartAbstract { + public void tick() { + super.tick(); + if (this.b > 0) { ++ // Paper start - Configurable TNT entity height nerf ++ if (this.world.paperConfig.entityTNTHeightNerf != 0 && this.locY() > this.world.paperConfig.entityTNTHeightNerf) { ++ this.die(); ++ return; ++ } ++ // Paper end + --this.b; + this.world.addParticle(Particles.SMOKE, this.locX(), this.locY() + 0.5D, this.locZ(), 0.0D, 0.0D, 0.0D); + } else if (this.b == 0) { diff --git a/patches/server-unmapped/0001/0019-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch b/patches/server-unmapped/0001/0019-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch new file mode 100644 index 0000000000..07b5db16ba --- /dev/null +++ b/patches/server-unmapped/0001/0019-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch @@ -0,0 +1,117 @@ +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/EULA.java b/src/main/java/net/minecraft/server/EULA.java +index a5171d28b960b12c2743ea68a36d747bc967697d..a0f53c9eff04a40780b3ba568dbfc5bbe9bd8504 100644 +--- a/src/main/java/net/minecraft/server/EULA.java ++++ b/src/main/java/net/minecraft/server/EULA.java +@@ -72,7 +72,7 @@ public class EULA { + Properties properties = new Properties(); + + properties.setProperty("eula", "false"); +- properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula)."); ++ properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula).\nYou also agree that tacos are tasty, and the best food in the world."); // Paper - fix lag; + } catch (Throwable throwable1) { + throwable = throwable1; + throw throwable1; +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 511d6094403d17522212fcdda6903a13517c44fa..9ba05ab7ec97896349f4b754f2993cda9ab1bbfd 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1342,7 +1342,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant // CraftBukkit - cb > vanilla! ++ return "Paper"; //Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! + } + + public CrashReport b(CrashReport crashreport) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 2f6b49e3ae7a7407eaec9810af8bf72e3a4896de..1818a6af3a55d15be698d5219e0eea63e2077611 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -226,7 +226,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 3b48799d084f14722f815cb35cbd48b618380fca..8efd3919bec5f497ce4bee041586c0a313931211 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -200,12 +200,25 @@ public class Main { + deadline.add(Calendar.DAY_OF_YEAR, -28); + 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 + 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 ++ + 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 b4eeeb0d001e692180874bd26385a0e786a8b752..1b3a14784cac8e855633fae6172ad5479ebe9877 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.World.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 + dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // diff --git a/patches/server-unmapped/0001/0020-Implement-Paper-VersionChecker.patch b/patches/server-unmapped/0001/0020-Implement-Paper-VersionChecker.patch new file mode 100644 index 0000000000..8be3bf335e --- /dev/null +++ b/patches/server-unmapped/0001/0020-Implement-Paper-VersionChecker.patch @@ -0,0 +1,145 @@ +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..c8b911e5d013525ffc5d2911ee0e421dd916cb00 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +@@ -0,0 +1,117 @@ ++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.format.NamedTextColor; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import java.io.*; ++import java.net.HttpURLConnection; ++import java.net.URL; ++ ++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 @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); ++ } ++ } ++ ++ private static int fetchDistanceFromSiteApi(int jenkinsBuild, @Nullable String siteApiVersion) { ++ if (siteApiVersion == null) { return -1; } ++ try { ++ try (BufferedReader reader = Resources.asCharSource( ++ new URL("https://papermc.io/api/v1/paper/" + siteApiVersion + "/latest"), ++ Charsets.UTF_8 ++ ).openBufferedStream()) { ++ JsonObject json = new Gson().fromJson(reader, JsonObject.class); ++ int latest = json.get("build").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 8f737f63f280c00c1276bd1dc3ecf60448732ca8..8aa9e7796ea39c09a965750d06c3d358250f33b8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -370,6 +370,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public String getTimingsServerName() { + return com.destroystokyo.paper.PaperConfig.timingsServerName; + } ++ ++ @Override ++ public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { ++ return new com.destroystokyo.paper.PaperVersionFetcher(); ++ } + // Paper end + + /** diff --git a/patches/server-unmapped/0001/0021-Add-version-history-to-version-command.patch b/patches/server-unmapped/0001/0021-Add-version-history-to-version-command.patch new file mode 100644 index 0000000000..8ce6b7fea5 --- /dev/null +++ b/patches/server-unmapped/0001/0021-Add-version-history-to-version-command.patch @@ -0,0 +1,215 @@ +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 c8b911e5d013525ffc5d2911ee0e421dd916cb00..dc0ea65ab87255fad0d54dfb509300098a0b4864 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java ++++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +@@ -5,7 +5,9 @@ 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.TextComponent; + import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextDecoration; + + import javax.annotation.Nonnull; + import javax.annotation.Nullable; +@@ -27,7 +29,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() { +@@ -114,4 +119,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..aac3f66cb23d260729c2a48d8710a9de2346aa22 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java +@@ -0,0 +1,145 @@ ++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 (!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 59942eb6bb6e8e1e9a988bce0d09757e575018b9..faf4d00bf288359db806913c4d2964324e8706b7 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -196,6 +196,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + return false; + } + com.destroystokyo.paper.PaperConfig.registerCommands(); ++ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now + // Paper end + + this.setPVP(dedicatedserverproperties.pvp); diff --git a/patches/server-unmapped/0001/0022-Player-affects-spawning-API.patch b/patches/server-unmapped/0001/0022-Player-affects-spawning-API.patch new file mode 100644 index 0000000000..93161ad718 --- /dev/null +++ b/patches/server-unmapped/0001/0022-Player-affects-spawning-API.patch @@ -0,0 +1,157 @@ +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/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 20e4ff812960a54872f2fea8fe6baf7bb1ef077d..cae9da158f54438d2a397665c7ce964f6f755469 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1354,6 +1354,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return MathHelper.c(f * f + f1 * f1 + f2 * f2); + } + ++ public double getDistanceSquared(double x, double y, double z) { return h(x, y, z); } // Paper - OBFHELPER + public double h(double d0, double d1, double d2) { + double d3 = this.locX() - d0; + double d4 = this.locY() - d1; +diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +index f93af56f68d5fd27eca38d333ca429ce22fc397b..0631cd531647239858b2a7298f58cc770720f69a 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java ++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +@@ -756,7 +756,7 @@ public abstract class EntityInsentient extends EntityLiving { + if (this.world.getDifficulty() == EnumDifficulty.PEACEFUL && this.L()) { + this.die(); + } else if (!this.isPersistent() && !this.isSpecialPersistence()) { +- EntityHuman entityhuman = this.world.findNearbyPlayer(this, -1.0D); ++ EntityHuman entityhuman = this.world.findNearbyPlayer(this, -1.0D, IEntitySelector.affectsSpawning); // Paper + + if (entityhuman != null) { + double d0 = entityhuman.h((Entity) this); // CraftBukkit - decompile error +diff --git a/src/main/java/net/minecraft/world/entity/IEntitySelector.java b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +index 6e78192a5898df017d96acba845a288011d24e35..dfcfdb31ca9531913d705aaaf85fb67399cfdc8c 100644 +--- a/src/main/java/net/minecraft/world/entity/IEntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +@@ -29,6 +29,12 @@ public final class IEntitySelector { + return !entity.isSpectator(); + }; + ++ // Paper start ++ public static final Predicate affectsSpawning = (entity) -> { ++ return !entity.isSpectator() && entity.isAlive() && (entity instanceof EntityPlayer) && ((EntityPlayer) entity).affectsSpawning; ++ }; ++ // Paper end ++ + public static Predicate a(double d0, double d1, double d2, double d3) { + double d4 = d3 * d3; + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntitySilverfish.java b/src/main/java/net/minecraft/world/entity/monster/EntitySilverfish.java +index 0b3b430766fba602e74727f78173567ca10fabc6..e1fcb1be102822e87eaf7757fbd64a516b2f58ac 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntitySilverfish.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntitySilverfish.java +@@ -122,7 +122,7 @@ public class EntitySilverfish extends EntityMonster { + if (c(entitytypes, generatoraccess, enummobspawn, blockposition, random)) { + EntityHuman entityhuman = generatoraccess.a((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.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/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 1017ee73b8617ce2b6734469fa49aaff7563c2b1..f42e16589476c1bd10b13214dda5ac7bb3e52131 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -164,6 +164,9 @@ public abstract class EntityHuman extends EntityLiving { + private final ItemCooldown bM; + @Nullable + public EntityFishingHook hookedFish; ++ // Paper start ++ public boolean affectsSpawning = true; ++ // Paper end + + // CraftBukkit start + public boolean fauxSleeping; +diff --git a/src/main/java/net/minecraft/world/level/IEntityAccess.java b/src/main/java/net/minecraft/world/level/IEntityAccess.java +index 4ece69851e7b05016f52c291ce911eb791cf3a23..6d5d4c3df65995b9a13b66d070ba08d553cc98a2 100644 +--- a/src/main/java/net/minecraft/world/level/IEntityAccess.java ++++ b/src/main/java/net/minecraft/world/level/IEntityAccess.java +@@ -92,8 +92,9 @@ public interface IEntityAccess { + } + } + +- @Nullable +- default EntityHuman a(double d0, double d1, double d2, double d3, @Nullable Predicate predicate) { ++ default EntityHuman findNearbyPlayer(Entity entity, double d0, @Nullable Predicate predicate) { return this.findNearbyPlayer(entity.locX(), entity.locY(), entity.locZ(), d0, predicate); } // Paper ++ @Nullable default EntityHuman findNearbyPlayer(double d0, double d1, double d2, double d3, @Nullable Predicate predicate) { return a(d0, d1, d2, d3, predicate); } // Paper - OBFHELPER ++ @Nullable default EntityHuman a(double d0, double d1, double d2, double d3, @Nullable Predicate predicate) { // Paper + double d4 = -1.0D; + EntityHuman entityhuman = null; + Iterator iterator = this.getPlayers().iterator(); +@@ -126,6 +127,27 @@ public interface IEntityAccess { + return this.a(d0, d1, d2, d3, predicate); + } + ++ // Paper end ++ default boolean isAffectsSpawningPlayerNearby(double d0, double d1, double d2, double d3) { ++ Iterator iterator = this.getPlayers().iterator(); ++ double d4; ++ do { ++ EntityHuman entityhuman; ++ do { ++ if (!iterator.hasNext()) { ++ return false; ++ } ++ ++ entityhuman = (EntityHuman) iterator.next(); ++ } while (!IEntitySelector.affectsSpawning.test(entityhuman)); ++ ++ d4 = entityhuman.getDistanceSquared(d0, d1, d2); ++ } while (d3 >= 0.0D && d4 >= d3 * d3); ++ ++ return true; ++ } ++ // Paper end ++ + default boolean isPlayerNearby(double d0, double d1, double d2, double d3) { + Iterator iterator = this.getPlayers().iterator(); + +diff --git a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +index 33cba4e475edc0573b901f70c61d3659fd63ad62..8d8b03074df1635946f81bec0feae18d2f3e20aa 100644 +--- a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java ++++ b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +@@ -66,7 +66,7 @@ public abstract class MobSpawnerAbstract { + private boolean h() { + BlockPosition blockposition = this.b(); + +- return this.a().isPlayerNearby((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, (double) this.requiredPlayerRange); ++ return this.a().isAffectsSpawningPlayerNearby((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper + } + + public void c() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 137870c7d18c9ef3ae637e83c5457d42ec40c669..e9f5ef73de6fd100dda7ed006dde6635f65b1ab5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1767,8 +1767,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + @Override + public String getLocale() { + return getHandle().locale; ++ ++ } ++ ++ // 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 (getHandle().playerConnection == null) return; diff --git a/patches/server-unmapped/0001/0023-Remove-invalid-mob-spawner-tile-entities.patch b/patches/server-unmapped/0001/0023-Remove-invalid-mob-spawner-tile-entities.patch new file mode 100644 index 0000000000..2aeae2d5b4 --- /dev/null +++ b/patches/server-unmapped/0001/0023-Remove-invalid-mob-spawner-tile-entities.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Tue, 1 Mar 2016 15:08:03 -0600 +Subject: [PATCH] Remove invalid mob spawner tile entities + + +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index ac576d268b23148089d404cb22d8c2f9d1a79d6e..a2d80c2c8e4f080f60746548f75631c5946ba8e2 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -40,9 +40,11 @@ import net.minecraft.world.level.TickListChunk; + import net.minecraft.world.level.TickListEmpty; + import net.minecraft.world.level.World; + import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.BlockMobSpawner; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.ITileEntity; + import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.entity.TileEntityMobSpawner; + import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.level.levelgen.ChunkProviderDebug; + import net.minecraft.world.level.levelgen.HeightMap; +@@ -648,6 +650,10 @@ public class Chunk implements IChunkAccess { + } + + // CraftBukkit start ++ // Paper start - Remove invalid mob spawner tile entities ++ } else if (tileentity instanceof TileEntityMobSpawner && !(getBlockData(blockposition.getX(), blockposition.getY(), blockposition.getZ()).getBlock() instanceof BlockMobSpawner)) { ++ this.tileEntities.remove(blockposition); ++ // Paper end + } else { + System.out.println("Attempted to place a tile entity (" + tileentity + ") at " + tileentity.getPosition().getX() + "," + tileentity.getPosition().getY() + "," + tileentity.getPosition().getZ() + + " (" + getType(blockposition) + ") where there was no entity tile!"); diff --git a/patches/server-unmapped/0001/0024-Optimize-TileEntity-Ticking.patch b/patches/server-unmapped/0001/0024-Optimize-TileEntity-Ticking.patch new file mode 100644 index 0000000000..a556c9c8da --- /dev/null +++ b/patches/server-unmapped/0001/0024-Optimize-TileEntity-Ticking.patch @@ -0,0 +1,248 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 8 Mar 2015 22:55:25 -0600 +Subject: [PATCH] Optimize TileEntity Ticking + + +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +index d4ebcf8f66197299256bd6b65710a1488c90ea41..c9164dfdb27ddf3709129c8aec54903a1df121ff 100644 +--- a/src/main/java/co/aikar/timings/TimingsExport.java ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -109,7 +109,7 @@ public class TimingsExport extends Thread { + pair("end", System.currentTimeMillis() / 1000), + pair("online-mode", Bukkit.getServer().getOnlineMode()), + pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000), +- pair("datapacks", toArrayMapper(MinecraftServer.getServer().getResourcePackRepository().d(), pack -> { ++ pair("datapacks", toArrayMapper(MinecraftServer.getServer().getResourcePackRepository().e(), pack -> { + // Don't feel like obf helper'ing these, non fatal if its temp missed. + return ChatColor.stripColor(CraftChatMessage.fromComponent(pack.a(true))); + })) +@@ -148,8 +148,8 @@ public class TimingsExport extends Thread { + ); + + parent.put("worlds", toObjectMapper(MinecraftServer.getServer().getWorlds(), world -> { +- if (world.getWorldData().getName().equals("worldeditregentempworld")) return null; +- return pair(world.getWorldData().getName(), createObject( ++ if (world.getWorld().getName().equals("worldeditregentempworld")) return null; ++ return pair(world.getWorld().getName(), createObject( + pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> { + return pair(rule, world.getWorld().getGameRuleValue(rule)); + })), +diff --git a/src/main/java/net/minecraft/world/level/block/BlockChest.java b/src/main/java/net/minecraft/world/level/block/BlockChest.java +index 60e7dc1910ae9214d84d65b011cfec278b6b32ae..b229faad99120c67b089f7680d800fbe594fe7da 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockChest.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockChest.java +@@ -54,8 +54,8 @@ import net.minecraft.world.phys.shapes.VoxelShapeCollision; + public class BlockChest extends BlockChestAbstract implements IBlockWaterlogged { + + public static final BlockStateDirection FACING = BlockFacingHorizontal.FACING; +- public static final BlockStateEnum c = BlockProperties.aF; +- public static final BlockStateBoolean d = BlockProperties.C; ++ public static final BlockStateEnum c = BlockProperties.aF; public static final BlockStateEnum CHEST_TYPE_PROPERTY = c; // Paper - OBFHELPER ++ public static final BlockStateBoolean d = BlockProperties.C; public static final BlockStateBoolean waterlogged() { return d; } // Paper OBFHELPER + protected static final VoxelShape e = Block.a(1.0D, 0.0D, 0.0D, 15.0D, 14.0D, 15.0D); + protected static final VoxelShape f = Block.a(1.0D, 0.0D, 1.0D, 15.0D, 14.0D, 16.0D); + protected static final VoxelShape g = Block.a(0.0D, 0.0D, 1.0D, 15.0D, 14.0D, 15.0D); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java +index eed95b370d1d624ffc6b7a35357b7028ec58c584..51167d776c710decb0107bebcb35bdf43103772b 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java +@@ -8,6 +8,7 @@ import net.minecraft.core.NonNullList; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.chat.ChatMessage; + import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundCategory; + import net.minecraft.sounds.SoundEffect; + import net.minecraft.sounds.SoundEffects; +@@ -33,7 +34,7 @@ import org.bukkit.craftbukkit.entity.CraftHumanEntity; + import org.bukkit.entity.HumanEntity; + // CraftBukkit end + +-public class TileEntityChest extends TileEntityLootable implements ITickable { ++public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITickable + + private NonNullList items; + protected float a; +@@ -111,14 +112,20 @@ public class TileEntityChest extends TileEntityLootable implements ITickable { + return nbttagcompound; + } + +- @Override + public void tick() { + int i = this.position.getX(); + int j = this.position.getY(); + int k = this.position.getZ(); + + ++this.j; +- this.viewingCount = a(this.world, this, this.j, i, j, k, this.viewingCount); ++ } ++ ++ public void doOpenLogic() { ++ int i = this.position.getX(); ++ int j = this.position.getY(); ++ int k = this.position.getZ(); ++ ++ //this.viewingCount = a(this.world, this, this.j, i, j, k, this.viewingCount); // Paper - check is faulty given our logic is called before active container set + this.b = this.a; + float f = 0.1F; + +@@ -132,8 +139,11 @@ public class TileEntityChest extends TileEntityLootable implements ITickable { + if (this.viewingCount > 0 && this.a == 0.0F) { + this.playOpenSound(SoundEffects.BLOCK_CHEST_OPEN); + } ++ } + +- if (this.viewingCount == 0 && this.a > 0.0F || this.viewingCount > 0 && this.a < 1.0F) { ++ public void doCloseLogic() { ++ if (this.viewingCount == 0 /* && this.a > 0.0F || this.viewingCount > 0 && this.a < 1.0F */) { // Paper - disable all but player count check ++ /* // Paper - disable animation stuff + float f1 = this.a; + + if (this.viewingCount > 0) { +@@ -149,8 +159,11 @@ public class TileEntityChest extends TileEntityLootable implements ITickable { + float f2 = 0.5F; + + if (this.a < 0.5F && f1 >= 0.5F) { ++ */ ++ MCUtil.scheduleTask(10, () -> { + this.playOpenSound(SoundEffects.BLOCK_CHEST_CLOSE); +- } ++ }, "Chest Sounds"); ++ //} // Paper end + + if (this.a < 0.0F) { + this.a = 0.0F; +@@ -189,6 +202,7 @@ public class TileEntityChest extends TileEntityLootable implements ITickable { + } + + public void playOpenSound(SoundEffect soundeffect) { ++ if (!this.getBlock().contains(BlockChest.CHEST_TYPE_PROPERTY)) { return; } // Paper - this can be delayed, double check exists - Fixes GH-2074 + BlockPropertyChestType blockpropertychesttype = (BlockPropertyChestType) this.getBlock().get(BlockChest.c); + + if (blockpropertychesttype != BlockPropertyChestType.LEFT) { +@@ -227,6 +241,7 @@ public class TileEntityChest extends TileEntityLootable implements ITickable { + + ++this.viewingCount; + if (this.world == null) return; // CraftBukkit ++ doOpenLogic(); // Paper + + // CraftBukkit start - Call redstone event + if (this.getBlock().getBlock() == Blocks.TRAPPED_CHEST) { +@@ -249,6 +264,7 @@ public class TileEntityChest extends TileEntityLootable implements ITickable { + --this.viewingCount; + + // CraftBukkit start - Call redstone event ++ doCloseLogic(); // Paper + if (this.getBlock().getBlock() == Blocks.TRAPPED_CHEST) { + int newPower = Math.max(0, Math.min(15, this.viewingCount)); + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnderChest.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnderChest.java +index 930f1bd091d9754f7ca5d9e36cdf49b2be03eb23..2bc4213c70be47ca8bbc24898cc92e43f4228821 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnderChest.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEnderChest.java +@@ -1,11 +1,12 @@ + package net.minecraft.world.level.block.entity; + ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundCategory; + import net.minecraft.sounds.SoundEffects; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.level.block.Blocks; + +-public class TileEntityEnderChest extends TileEntity implements ITickable { ++public class TileEntityEnderChest extends TileEntity { // Paper - Remove ITickable + + public float a; + public float b; +@@ -16,18 +17,28 @@ public class TileEntityEnderChest extends TileEntity implements ITickable { + super(TileEntityTypes.ENDER_CHEST); + } + +- @Override + public void tick() { + if (++this.g % 20 * 4 == 0) { + this.world.playBlockAction(this.position, Blocks.ENDER_CHEST, 1, this.c); + } + + this.b = this.a; ++ /* // Paper + int i = this.position.getX(); + int j = this.position.getY(); + int k = this.position.getZ(); + float f = 0.1F; + double d0; ++ // Paper start ++ */ ++ } ++ ++ private void doOpenLogic() { ++ int i = this.position.getX(); ++ int j = this.position.getY(); ++ int k = this.position.getZ(); ++ double d0; ++ // Paper end + + if (this.c > 0 && this.a == 0.0F) { + double d1 = (double) i + 0.5D; +@@ -35,8 +46,17 @@ public class TileEntityEnderChest extends TileEntity implements ITickable { + d0 = (double) k + 0.5D; + this.world.playSound((EntityHuman) null, d1, (double) j + 0.5D, d0, SoundEffects.BLOCK_ENDER_CHEST_OPEN, SoundCategory.BLOCKS, 0.5F, this.world.random.nextFloat() * 0.1F + 0.9F); + } ++ // Paper start ++ } + +- if (this.c == 0 && this.a > 0.0F || this.c > 0 && this.a < 1.0F) { ++ private void doCloseLogic() { ++ int i = this.position.getX(); ++ int j = this.position.getY(); ++ int k = this.position.getZ(); ++ double d0; ++ ++ if (this.c == 0) { /* && this.a > 0.0F || this.c > 0 && this.a < 1.0F) { ++ // Paper end + float f1 = this.a; + + if (this.c > 0) { +@@ -52,11 +72,14 @@ public class TileEntityEnderChest extends TileEntity implements ITickable { + float f2 = 0.5F; + + if (this.a < 0.5F && f1 >= 0.5F) { ++ // Paper start ++ */ + d0 = (double) i + 0.5D; + double d2 = (double) k + 0.5D; + ++ MCUtil.scheduleTask(10, () -> { + this.world.playSound((EntityHuman) null, d0, (double) j + 0.5D, d2, SoundEffects.BLOCK_ENDER_CHEST_CLOSE, SoundCategory.BLOCKS, 0.5F, this.world.random.nextFloat() * 0.1F + 0.9F); +- } ++ }, "Chest Sounds"); + + if (this.a < 0.0F) { + this.a = 0.0F; +@@ -84,11 +107,13 @@ public class TileEntityEnderChest extends TileEntity implements ITickable { + public void d() { + ++this.c; + this.world.playBlockAction(this.position, Blocks.ENDER_CHEST, 1, this.c); ++ doOpenLogic(); // Paper + } + + public void f() { + --this.c; + this.world.playBlockAction(this.position, Blocks.ENDER_CHEST, 1, this.c); ++ doCloseLogic(); // Paper + } + + public boolean a(EntityHuman entityhuman) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/IBlockDataHolder.java b/src/main/java/net/minecraft/world/level/block/state/IBlockDataHolder.java +index e4b59a85ee9b435b2e86d4c7d78b7224773f6967..ba046cffdd8331c7e0427f19fa54d0c7a99077d9 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/IBlockDataHolder.java ++++ b/src/main/java/net/minecraft/world/level/block/state/IBlockDataHolder.java +@@ -84,6 +84,7 @@ public abstract class IBlockDataHolder { + return Collections.unmodifiableCollection(this.b.keySet()); + } + ++ public > boolean contains(IBlockState iblockstate) { return this.b(iblockstate); } // Paper - OBFHELPER + public > boolean b(IBlockState iblockstate) { + return this.b.containsKey(iblockstate); + } diff --git a/patches/server-unmapped/0001/0025-Further-improve-server-tick-loop.patch b/patches/server-unmapped/0001/0025-Further-improve-server-tick-loop.patch new file mode 100644 index 0000000000..fbb9aadb92 --- /dev/null +++ b/patches/server-unmapped/0001/0025-Further-improve-server-tick-loop.patch @@ -0,0 +1,208 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 1 Mar 2016 23:09:29 -0600 +Subject: [PATCH] Further improve server tick loop + +Improves how the catchup buffer is handled, allowing it to roll both ways +increasing the effeciency of the thread sleep so it only will sleep once. + +Also increases the buffer of the catchup to ensure server stays at 20 TPS unless extreme conditions + +Previous implementation did not calculate TPS correctly. +Switch to a realistic rolling average and factor in std deviation as an extra reporting variable + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 9ba05ab7ec97896349f4b754f2993cda9ab1bbfd..add4f149fd31d1420d825b646b3e088808e5896b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -253,7 +253,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; + public CommandDispatcher vanillaCommandDispatcher; +@@ -262,7 +262,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 5000L && this.nextTick - this.lastOverloadTime >= 30000L) { // CraftBukkit + long j = i / 50L; + + if (server.getWarnOnOverload()) // CraftBukkit +- MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", i, j); ++ MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", i, j); + this.nextTick += j * 50L; + this.lastOverloadTime = this.nextTick; + } + +- if ( tickCount++ % SAMPLE_INTERVAL == 0 ) ++ if ( ++MinecraftServer.currentTick % SAMPLE_INTERVAL == 0 ) + { +- double currentTps = 1E3 / ( curTime - tickSection ) * SAMPLE_INTERVAL; +- recentTps[0] = calcTps( recentTps[0], 0.92, currentTps ); // 1/exp(5sec/1min) +- recentTps[1] = calcTps( recentTps[1], 0.9835, currentTps ); // 1/exp(5sec/5min) +- recentTps[2] = calcTps( recentTps[2], 0.9945, currentTps ); // 1/exp(5sec/15min) ++ final long diff = curTime - tickSection; ++ java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP); ++ tps1.add(currentTps, diff); ++ tps5.add(currentTps, diff); ++ tps15.add(currentTps, diff); ++ // Backwards compat with bad plugins ++ recentTps[0] = tps1.getAverage(); ++ recentTps[1] = tps5.getAverage(); ++ recentTps[2] = tps15.getAverage(); ++ // Paper end + tickSection = curTime; + } + // Spigot end + +- MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit ++ //MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time ++ lastTick = curTime; + this.nextTick += 50L; + GameProfilerTick gameprofilertick = GameProfilerTick.a("Server"); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 1818a6af3a55d15be698d5219e0eea63e2077611..f84fe5929cb7bcedff5fc587163380172bc1e8be 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2113,6 +2113,17 @@ public final class CraftServer implements Server { + return CraftMagicNumbers.INSTANCE; + } + ++ // Paper - Add getTPS API - Further improve tick loop ++ @Override ++ public double[] getTPS() { ++ return new double[] { ++ net.minecraft.server.MinecraftServer.getServer().tps1.getAverage(), ++ net.minecraft.server.MinecraftServer.getServer().tps5.getAverage(), ++ net.minecraft.server.MinecraftServer.getServer().tps15.getAverage() ++ }; ++ } ++ // Paper end ++ + // Spigot start + private final org.bukkit.Server.Spigot spigot = new org.bukkit.Server.Spigot() + { +diff --git a/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/src/main/java/org/spigotmc/TicksPerSecondCommand.java +index f5b6dec1cbe7501ce2ee9125920e810bc94670cc..e62890433ffbe0b4e48942fe6c38b599a19e58fd 100644 +--- a/src/main/java/org/spigotmc/TicksPerSecondCommand.java ++++ b/src/main/java/org/spigotmc/TicksPerSecondCommand.java +@@ -24,22 +24,30 @@ public class TicksPerSecondCommand extends Command + return true; + } + +- StringBuilder sb = new StringBuilder( ChatColor.GOLD + "TPS from last 1m, 5m, 15m: " ); +- for ( double tps : MinecraftServer.getServer().recentTps ) +- { +- sb.append( format( tps ) ); +- sb.append( ", " ); ++ // Paper start - Further improve tick handling ++ double[] tps = org.bukkit.Bukkit.getTPS(); ++ String[] tpsAvg = new String[tps.length]; ++ ++ for ( int i = 0; i < tps.length; i++) { ++ tpsAvg[i] = format( tps[i] ); ++ } ++ sender.sendMessage(ChatColor.GOLD + "TPS from last 1m, 5m, 15m: " + org.apache.commons.lang.StringUtils.join(tpsAvg, ", ")); ++ if (args.length > 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) { ++ sender.sendMessage(ChatColor.GOLD + "Current Memory Usage: " + ChatColor.GREEN + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)"); ++ if (!hasShownMemoryWarning) { ++ sender.sendMessage(ChatColor.RED + "Warning: " + ChatColor.GOLD + " Memory usage on modern garbage collectors is not a stable value and it is perfectly normal to see it reach max. Please do not pay it much attention."); ++ hasShownMemoryWarning = true; ++ } + } +- sender.sendMessage( sb.substring( 0, sb.length() - 2 ) ); +- sender.sendMessage(ChatColor.GOLD + "Current Memory Usage: " + ChatColor.GREEN + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " +- + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)"); ++ // Paper end + + return true; + } + +- private String format(double tps) ++ private boolean hasShownMemoryWarning; // Paper ++ private static String format(double tps) // Paper - Made static + { + return ( ( tps > 18.0 ) ? ChatColor.GREEN : ( tps > 16.0 ) ? ChatColor.YELLOW : ChatColor.RED ).toString() +- + ( ( tps > 20.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); ++ + ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise + } + } diff --git a/patches/server-unmapped/0001/0026-Only-refresh-abilities-if-needed.patch b/patches/server-unmapped/0001/0026-Only-refresh-abilities-if-needed.patch new file mode 100644 index 0000000000..f25e87a2ce --- /dev/null +++ b/patches/server-unmapped/0001/0026-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 e9f5ef73de6fd100dda7ed006dde6635f65b1ab5..62f8d96f996ece87b7ab8d5d05d1dc214d10dbfa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1441,12 +1441,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void setFlying(boolean value) { ++ boolean needsUpdate = getHandle().abilities.isFlying != value; // Paper - Only refresh abilities if needed + if (!getAllowFlight() && value) { + throw new IllegalArgumentException("Cannot make player fly if getAllowFlight() is false"); + } + + getHandle().abilities.isFlying = value; +- getHandle().updateAbilities(); ++ if (needsUpdate) getHandle().updateAbilities(); // Paper - Only refresh abilities if needed + } + + @Override diff --git a/patches/server-unmapped/0001/0027-Entity-Origin-API.patch b/patches/server-unmapped/0001/0027-Entity-Origin-API.patch new file mode 100644 index 0000000000..60fd7b5fbe --- /dev/null +++ b/patches/server-unmapped/0001/0027-Entity-Origin-API.patch @@ -0,0 +1,135 @@ +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/nbt/NBTTagList.java b/src/main/java/net/minecraft/nbt/NBTTagList.java +index 4f6f6f51f9807bafa88482c0fe776c8b163107d7..ce6572df63c4e7341708aee60330fb214a3fe416 100644 +--- a/src/main/java/net/minecraft/nbt/NBTTagList.java ++++ b/src/main/java/net/minecraft/nbt/NBTTagList.java +@@ -190,6 +190,7 @@ public class NBTTagList extends NBTList { + return new int[0]; + } + ++ public final double getDoubleAt(int i) { return this.h(i); } // Paper - OBFHELPER + public double h(int i) { + if (i >= 0 && i < this.list.size()) { + NBTBase nbtbase = (NBTBase) this.list.get(i); +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 4170743875d2fb16987e513713b2d141918219a5..701b00e65c4d5eb66e974f8d622eecef0f744f82 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1246,6 +1246,11 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.navigators.add(((EntityInsentient) entity).getNavigation()); + } + entity.valid = true; // CraftBukkit ++ // Paper start - Set origin location when the entity is being added to the world ++ if (entity.origin == null) { ++ entity.origin = entity.getBukkitEntity().getLocation(); ++ } ++ // Paper end + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index cae9da158f54438d2a397665c7ce964f6f755469..83006297e59971f4c358eacc3da586417982540c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -247,6 +247,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only + public boolean forceExplosionKnockback; // SPIGOT-949 + public boolean persistentInvisibility = false; ++ public org.bukkit.Location origin; // Paper + // Spigot start + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; +@@ -1625,6 +1626,11 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + this.bukkitEntity.storeBukkitValues(nbttagcompound); + } + // CraftBukkit end ++ // Paper start - Save the entity's origin location ++ if (this.origin != null) { ++ nbttagcompound.set("Paper.Origin", this.createList(origin.getX(), origin.getY(), origin.getZ())); ++ } ++ // Paper end + return nbttagcompound; + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.a(throwable, "Saving entity NBT"); +@@ -1747,6 +1753,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + // CraftBukkit end + ++ // Paper start - Restore the entity's origin location ++ NBTTagList originTag = nbttagcompound.getList("Paper.Origin", 6); ++ if (!originTag.isEmpty()) { ++ origin = new org.bukkit.Location(world.getWorld(), originTag.getDoubleAt(0), originTag.getDoubleAt(1), originTag.getDoubleAt(2)); ++ } ++ // Paper end ++ + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.a(throwable, "Loading entity NBT"); + CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity being loaded"); +@@ -1808,6 +1821,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + protected abstract void saveData(NBTTagCompound nbttagcompound); + ++ protected final NBTTagList createList(double... adouble) { return a(adouble); } // Paper - OBFHELPER + protected NBTTagList a(double... adouble) { + NBTTagList nbttaglist = new NBTTagList(); + double[] adouble1 = adouble; +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java b/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java +index 901522f24b8bc58861e46eda400dbab92bb6401d..3f10e41b18e09186635fd6f7c653b04db7b39d8e 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java +@@ -293,6 +293,14 @@ public class EntityFallingBlock extends Entity { + this.block = Blocks.SAND.getBlockData(); + } + ++ // Paper start - Try and load origin location from the old NBT tags for backwards compatibility ++ if (nbttagcompound.hasKey("SourceLoc_x")) { ++ int srcX = nbttagcompound.getInt("SourceLoc_x"); ++ int srcY = nbttagcompound.getInt("SourceLoc_y"); ++ int srcZ = nbttagcompound.getInt("SourceLoc_z"); ++ origin = new org.bukkit.Location(world.getWorld(), srcX, srcY, srcZ); ++ } ++ // Paper end + } + + public void a(boolean flag) { +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java b/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java +index 4f4b2b8d58223fa22d6a7af5c94cfb36399b9641..535e7d7297d81026b8586d5049b72fa65519b464 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java +@@ -120,6 +120,14 @@ public class EntityTNTPrimed extends Entity { + @Override + protected void loadData(NBTTagCompound nbttagcompound) { + this.setFuseTicks(nbttagcompound.getShort("Fuse")); ++ // Paper start - Try and load origin location from the old NBT tags for backwards compatibility ++ if (nbttagcompound.hasKey("SourceLoc_x")) { ++ int srcX = nbttagcompound.getInt("SourceLoc_x"); ++ int srcY = nbttagcompound.getInt("SourceLoc_y"); ++ int srcZ = nbttagcompound.getInt("SourceLoc_z"); ++ origin = new org.bukkit.Location(world.getWorld(), srcX, srcY, srcZ); ++ } ++ // Paper end + } + + @Nullable +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 3cf81734c8580f4d88ea97b6ac737a370b413c84..220bad90bbb9a90c3f23562bf0fb109fce379682 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1062,4 +1062,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return spigot; + } + // Spigot end ++ ++ // Paper start ++ @Override ++ public Location getOrigin() { ++ Location origin = getHandle().origin; ++ return origin == null ? null : origin.clone(); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0028-Prevent-tile-entity-and-entity-crashes.patch b/patches/server-unmapped/0001/0028-Prevent-tile-entity-and-entity-crashes.patch new file mode 100644 index 0000000000..94ae62120b --- /dev/null +++ b/patches/server-unmapped/0001/0028-Prevent-tile-entity-and-entity-crashes.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 1 Mar 2016 23:52:34 -0600 +Subject: [PATCH] Prevent tile entity and entity crashes + + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index d2848c8b03ca2ffa2d164fedb11ee22746bbf10b..3fd3c3883cec651fafc5b8bc39660ddedeb98ad1 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -737,11 +737,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + gameprofilerfiller.exit(); + } catch (Throwable throwable) { +- CrashReport crashreport = CrashReport.a(throwable, "Ticking block entity"); +- CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Block entity being ticked"); +- +- tileentity.a(crashreportsystemdetails); +- throw new ReportedException(crashreport); ++ // Paper start - Prevent tile entity and entity crashes ++ System.err.println("TileEntity threw exception at " + tileentity.world.getWorld().getName() + ":" + tileentity.position.getX() + "," + tileentity.position.getY() + "," + tileentity.position.getZ()); ++ throwable.printStackTrace(); ++ tilesThisCycle--; ++ this.tileEntityListTick.remove(tileTickPosition--); ++ continue; ++ // Paper end + // Spigot start + } finally { + tileentity.tickTimer.stopTiming(); +@@ -806,11 +808,12 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + try { + consumer.accept(entity); + } catch (Throwable throwable) { +- CrashReport crashreport = CrashReport.a(throwable, "Ticking entity"); +- CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity being ticked"); +- +- entity.appendEntityCrashDetails(crashreportsystemdetails); +- throw new ReportedException(crashreport); ++ // Paper start - Prevent tile entity and entity crashes ++ System.err.println("Entity threw exception at " + entity.world.getWorld().getName() + ":" + entity.locX() + "," + entity.locY() + "," + entity.locZ()); ++ throwable.printStackTrace(); ++ entity.dead = true; ++ return; ++ // Paper end + } + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +index 9ebd91e1309938f81583eb3d4dd97fd39bcc930a..58789a6e285c31947508deae37caefe7e182278c 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +@@ -208,7 +208,12 @@ public abstract class TileEntity implements net.minecraft.server.KeyedObject { / + return IRegistry.BLOCK_ENTITY_TYPE.getKey(this.getTileType()) + " // " + this.getClass().getCanonicalName(); + }); + if (this.world != null) { +- CrashReportSystemDetails.a(crashreportsystemdetails, this.position, this.getBlock()); ++ // Paper start - Prevent TileEntity and Entity crashes ++ IBlockData block = this.getBlock(); ++ if (block != null) { ++ CrashReportSystemDetails.a(crashreportsystemdetails, this.position, block); ++ } ++ // Paper end + CrashReportSystemDetails.a(crashreportsystemdetails, this.position, this.world.getType(this.position)); + } + } diff --git a/patches/server-unmapped/0001/0029-Configurable-top-of-nether-void-damage.patch b/patches/server-unmapped/0001/0029-Configurable-top-of-nether-void-damage.patch new file mode 100644 index 0000000000..9473d7010c --- /dev/null +++ b/patches/server-unmapped/0001/0029-Configurable-top-of-nether-void-damage.patch @@ -0,0 +1,89 @@ +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 + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index d16ae924bcbe31c964f7fb448757c748e5c4418c..4bba6977a0287837b8927718c040ac61463f0469 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -134,4 +134,19 @@ public class PaperWorldConfig { + if (fallingBlockHeightNerf != 0) log("Falling Block Height Limit set to Y: " + fallingBlockHeightNerf); + if (entityTNTHeightNerf != 0) log("TNT Entity Height Limit set to Y: " + entityTNTHeightNerf); + } ++ ++ public int netherVoidTopDamageHeight; ++ public boolean doNetherTopVoidDamage() { return netherVoidTopDamageHeight > 0; } ++ private void netherVoidTopDamageHeight() { ++ netherVoidTopDamageHeight = getInt("nether-ceiling-void-damage-height", 0); ++ log("Top of the nether void damage height: " + netherVoidTopDamageHeight); ++ ++ if (PaperConfig.version < 18) { ++ boolean legacy = getBoolean("nether-ceiling-void-damage", false); ++ if (legacy) { ++ netherVoidTopDamageHeight = 128; ++ set("nether-ceiling-void-damage-height", netherVoidTopDamageHeight); ++ } ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 83006297e59971f4c358eacc3da586417982540c..75734c1db3baecfa5b89c74335a537c3f7a07c2e 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -500,9 +500,16 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + this.fallDistance *= 0.5F; + } + ++ // Paper start - Configurable nether ceiling damage ++ ++ // Extracted to own function ++ /* + if (this.locY() < -64.0D) { + this.an(); + } ++ */ ++ this.performVoidDamage(); ++ // Paper end + + if (!this.world.isClientSide) { + this.setFlag(0, this.fireTicks > 0); +@@ -595,6 +602,17 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + this.setFireTicks(0); + } + ++ // Paper start ++ protected void performVoidDamage() { ++ if (this.locY() < -64.0D || (this.world.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER ++ && world.paperConfig.doNetherTopVoidDamage() ++ && this.locY() >= world.paperConfig.netherVoidTopDamageHeight)) { ++ this.doVoidDamage(); ++ } ++ } ++ // Paper end ++ ++ protected final void doVoidDamage() { this.an(); } // Paper - OBFHELPER + protected void an() { + this.die(); + } +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java +index 37785b27c3d9ffd010f09f53b2ca09941f609872..1f94cc096d95129d85a6278b1e369729df93d27d 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java +@@ -330,9 +330,15 @@ public abstract class EntityMinecartAbstract extends Entity { + this.setDamage(this.getDamage() - 1.0F); + } + ++ // Paper start - Configurable nether ceiling damage ++ // Extracted to own function ++ /* + if (this.locY() < -64.0D) { + this.an(); + } ++ */ ++ this.performVoidDamage(); ++ // Paper end + + // this.doPortalTick(); // CraftBukkit - handled in postTick + if (this.world.isClientSide) { diff --git a/patches/server-unmapped/0001/0030-Check-online-mode-before-converting-and-renaming-pla.patch b/patches/server-unmapped/0001/0030-Check-online-mode-before-converting-and-renaming-pla.patch new file mode 100644 index 0000000000..386f7f1615 --- /dev/null +++ b/patches/server-unmapped/0001/0030-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/WorldNBTStorage.java b/src/main/java/net/minecraft/world/level/storage/WorldNBTStorage.java +index c3a1b5943b0dad8701d566c45b9b474dac7e5c8a..191c9e9a00b9871038f60d54bc22620322f6bdbd 100644 +--- a/src/main/java/net/minecraft/world/level/storage/WorldNBTStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/WorldNBTStorage.java +@@ -56,7 +56,7 @@ public class WorldNBTStorage { + File file = new File(this.playerDir, entityhuman.getUniqueIDString() + ".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:" + entityhuman.getName() ).getBytes( "UTF-8" ) ).toString() + ".dat"); + if ( file.exists() ) diff --git a/patches/server-unmapped/0001/0031-Always-tick-falling-blocks.patch b/patches/server-unmapped/0001/0031-Always-tick-falling-blocks.patch new file mode 100644 index 0000000000..4352384dba --- /dev/null +++ b/patches/server-unmapped/0001/0031-Always-tick-falling-blocks.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 2 Mar 2016 00:32:25 -0600 +Subject: [PATCH] Always tick falling blocks + + +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 69c5d4e51ebf747d931fadc819973e36f001f5bc..58d22363124a9343188d8c19476e5a92f2f0b80b 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -91,6 +91,7 @@ public class ActivationRange + || entity instanceof EntityFireball + || entity instanceof EntityLightning + || entity instanceof EntityTNTPrimed ++ || entity instanceof EntityFallingBlock // Paper - Always tick falling blocks + || entity instanceof EntityEnderCrystal + || entity instanceof EntityFireworks + || entity instanceof EntityThrownTrident ) diff --git a/patches/server-unmapped/0001/0032-Configurable-end-credits.patch b/patches/server-unmapped/0001/0032-Configurable-end-credits.patch new file mode 100644 index 0000000000..da21a59d2f --- /dev/null +++ b/patches/server-unmapped/0001/0032-Configurable-end-credits.patch @@ -0,0 +1,42 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 4bba6977a0287837b8927718c040ac61463f0469..e6e18f309dc09ea9416ea37dcc697ddc2b571a96 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -149,4 +149,10 @@ public class PaperWorldConfig { + } + } + } ++ ++ public boolean disableEndCredits; ++ private void disableEndCredits() { ++ disableEndCredits = getBoolean("game-mechanics.disable-end-credits", false); ++ log("End credits disabled: " + disableEndCredits); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index bb309790dc90aedabb3c48ea21cd87d1819d2261..68c3cde85f9b40c8638146dde0045822f0b123db 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -190,7 +190,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + private long ca = SystemUtils.getMonotonicMillis(); + private Entity spectatedEntity; + public boolean worldChangeInvuln; +- private boolean cd; ++ private boolean cd; private void setHasSeenCredits(boolean has) { this.cd = has; } // Paper - OBFHELPER + private final RecipeBookServer recipeBook = new RecipeBookServer(); + private Vec3D cf; + private int cg; +@@ -895,6 +895,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.decouple(); + this.getWorldServer().removePlayer(this); + if (!this.viewingCredits) { ++ if (world.paperConfig.disableEndCredits) this.setHasSeenCredits(true); // Paper - Toggle to always disable end credits + this.viewingCredits = true; + this.playerConnection.sendPacket(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.e, this.cd ? 0.0F : 1.0F)); + this.cd = true; diff --git a/patches/server-unmapped/0001/0033-Fix-lag-from-explosions-processing-dead-entities.patch b/patches/server-unmapped/0001/0033-Fix-lag-from-explosions-processing-dead-entities.patch new file mode 100644 index 0000000000..85ee818286 --- /dev/null +++ b/patches/server-unmapped/0001/0033-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 9c1ad56e4362cd86e7ffe2aef7fd9ec301cf9002..7786a06ba09aacaa70c346e85a9eeed9f2ffec6e 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -182,7 +182,7 @@ public class Explosion { + int i1 = MathHelper.floor(this.posY + (double) f2 + 1.0D); + int j1 = MathHelper.floor(this.posZ - (double) f2 - 1.0D); + int k1 = MathHelper.floor(this.posZ + (double) f2 + 1.0D); +- List list = this.world.getEntities(this.source, new AxisAlignedBB((double) i, (double) l, (double) j1, (double) j, (double) i1, (double) k1)); ++ List list = this.world.getEntities(this.source, new AxisAlignedBB((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 + Vec3D vec3d = new Vec3D(this.posX, this.posY, this.posZ); + + for (int l1 = 0; l1 < list.size(); ++l1) { diff --git a/patches/server-unmapped/0001/0034-Optimize-explosions.patch b/patches/server-unmapped/0001/0034-Optimize-explosions.patch new file mode 100644 index 0000000000..349744a348 --- /dev/null +++ b/patches/server-unmapped/0001/0034-Optimize-explosions.patch @@ -0,0 +1,148 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index e6e18f309dc09ea9416ea37dcc697ddc2b571a96..4881b03d470646843bad1bc343eb6a6ab9072d8e 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -155,4 +155,10 @@ public class PaperWorldConfig { + disableEndCredits = getBoolean("game-mechanics.disable-end-credits", false); + log("End credits disabled: " + disableEndCredits); + } ++ ++ public boolean optimizeExplosions; ++ private void optimizeExplosions() { ++ optimizeExplosions = getBoolean("optimize-explosions", false); ++ log("Optimize explosions: " + optimizeExplosions); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index add4f149fd31d1420d825b646b3e088808e5896b..06071e15851d5d27f1c9a0d60a764a6214e0ba0f 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1326,6 +1326,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant>> 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/World.java b/src/main/java/net/minecraft/world/level/World.java +index 3fd3c3883cec651fafc5b8bc39660ddedeb98ad1..606e54d49875eb2da4cc5b2857b8727bb6469f74 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -136,6 +136,7 @@ public abstract class World implements GeneratorAccess, 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-unmapped/0001/0035-Disable-explosion-knockback.patch b/patches/server-unmapped/0001/0035-Disable-explosion-knockback.patch new file mode 100644 index 0000000000..860a43082b --- /dev/null +++ b/patches/server-unmapped/0001/0035-Disable-explosion-knockback.patch @@ -0,0 +1,69 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 4881b03d470646843bad1bc343eb6a6ab9072d8e..2222c1bb5f8625eee4d88946e4bfdfa2fe598977 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -161,4 +161,9 @@ public class PaperWorldConfig { + optimizeExplosions = getBoolean("optimize-explosions", false); + log("Optimize explosions: " + optimizeExplosions); + } ++ ++ public boolean disableExplosionKnockback; ++ private void disableExplosionKnockback(){ ++ disableExplosionKnockback = getBoolean("disable-explosion-knockback", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 661c3e2f12de36167bff149a3d979c4581402cbc..4e22a5e6d01506a681639500e90228e97d5772bb 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -1283,6 +1283,7 @@ public abstract class EntityLiving extends Entity { + } + } + ++ boolean knockbackCancelled = world.paperConfig.disableExplosionKnockback && damagesource.isExplosion() && this instanceof EntityHuman; // Paper - Disable explosion knockback + if (flag1) { + if (flag) { + this.world.broadcastEntityEffect(this, (byte) 29); +@@ -1301,6 +1302,7 @@ public abstract class EntityLiving extends Entity { + b0 = 2; + } + ++ if (!knockbackCancelled) // Paper - Disable explosion knockback + this.world.broadcastEntityEffect(this, b0); + } + +@@ -1324,6 +1326,7 @@ public abstract class EntityLiving extends Entity { + } + } + ++ if (knockbackCancelled) this.world.broadcastEntityEffect(this, (byte) 2); // Paper - Disable explosion knockback + if (this.dl()) { + if (!this.f(damagesource)) { + SoundEffect soundeffect = this.getSoundDeath(); +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 6a7af2c0c3c294b10c6ddbf98babb0f30d7d5f56..618cf4e0d71b4b04085807314e79a02785f8a498 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -217,14 +217,14 @@ public class Explosion { + double d14 = d13; + + if (entity instanceof EntityLiving) { +- d14 = EnchantmentProtection.a((EntityLiving) entity, d13); ++ d14 = entity instanceof EntityHuman && world.paperConfig.disableExplosionKnockback ? 0 : EnchantmentProtection.a((EntityLiving) entity, d13); // Paper - Disable explosion knockback + } + + entity.setMot(entity.getMot().add(d8 * d14, d9 * d14, d10 * d14)); + if (entity instanceof EntityHuman) { + EntityHuman entityhuman = (EntityHuman) entity; + +- if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.abilities.isFlying)) { ++ if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.abilities.isFlying) && !world.paperConfig.disableExplosionKnockback) { // Paper - Disable explosion knockback + this.n.put(entityhuman, new Vec3D(d8 * d13, d9 * d13, d10 * d13)); + } + } diff --git a/patches/server-unmapped/0001/0036-Disable-thunder.patch b/patches/server-unmapped/0001/0036-Disable-thunder.patch new file mode 100644 index 0000000000..9887bf87d8 --- /dev/null +++ b/patches/server-unmapped/0001/0036-Disable-thunder.patch @@ -0,0 +1,33 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 2222c1bb5f8625eee4d88946e4bfdfa2fe598977..083e421f8496b5336af473b108498ed28b984774 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -166,4 +166,9 @@ public class PaperWorldConfig { + private void disableExplosionKnockback(){ + disableExplosionKnockback = getBoolean("disable-explosion-knockback", false); + } ++ ++ public boolean disableThunder; ++ private void disableThunder() { ++ disableThunder = getBoolean("disable-thunder", false); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 701b00e65c4d5eb66e974f8d622eecef0f744f82..6e8e9067a43d0fb88683be733fb3d138f35d6cf0 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -586,7 +586,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + gameprofilerfiller.enter("thunder"); + BlockPosition blockposition; + +- if (flag && this.W() && this.random.nextInt(100000) == 0) { ++ if (!this.paperConfig.disableThunder && flag && this.W() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder + blockposition = this.a(this.a(j, 0, k, 15)); + if (this.isRainingAt(blockposition)) { + DifficultyDamageScaler difficultydamagescaler = this.getDamageScaler(blockposition); diff --git a/patches/server-unmapped/0001/0037-Disable-ice-and-snow.patch b/patches/server-unmapped/0001/0037-Disable-ice-and-snow.patch new file mode 100644 index 0000000000..911b86d032 --- /dev/null +++ b/patches/server-unmapped/0001/0037-Disable-ice-and-snow.patch @@ -0,0 +1,33 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 083e421f8496b5336af473b108498ed28b984774..2f7a5a4a5a7b29750cfd777e0bc5d19a14e93fa2 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -171,4 +171,9 @@ public class PaperWorldConfig { + private void disableThunder() { + disableThunder = getBoolean("disable-thunder", false); + } ++ ++ public boolean disableIceAndSnow; ++ private void disableIceAndSnow(){ ++ disableIceAndSnow = getBoolean("disable-ice-and-snow", false); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 6e8e9067a43d0fb88683be733fb3d138f35d6cf0..474e91b1a0a3fc2c5abb238f058e50ad787c22d9 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -610,7 +610,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + gameprofilerfiller.exitEnter("iceandsnow"); +- if (this.random.nextInt(16) == 0) { ++ if (!this.paperConfig.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow + blockposition = this.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, this.a(j, 0, k, 15)); + BlockPosition blockposition1 = blockposition.down(); + BiomeBase biomebase = this.getBiome(blockposition); diff --git a/patches/server-unmapped/0001/0038-Configurable-mob-spawner-tick-rate.patch b/patches/server-unmapped/0001/0038-Configurable-mob-spawner-tick-rate.patch new file mode 100644 index 0000000000..0ced9bb837 --- /dev/null +++ b/patches/server-unmapped/0001/0038-Configurable-mob-spawner-tick-rate.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Wed, 2 Mar 2016 15:03:53 -0600 +Subject: [PATCH] Configurable mob spawner tick rate + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 2f7a5a4a5a7b29750cfd777e0bc5d19a14e93fa2..4de86b09c6bc3c1974ce61b550ccb73d37f6f170 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -176,4 +176,9 @@ public class PaperWorldConfig { + private void disableIceAndSnow(){ + disableIceAndSnow = getBoolean("disable-ice-and-snow", false); + } ++ ++ public int mobSpawnerTickRate; ++ private void mobSpawnerTickRate() { ++ mobSpawnerTickRate = getInt("mob-spawner-tick-rate", 1); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +index 8d8b03074df1635946f81bec0feae18d2f3e20aa..76c98d576d3e567ec4482b30219f5a9107cb9703 100644 +--- a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java ++++ b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +@@ -41,6 +41,7 @@ public abstract class MobSpawnerAbstract { + public int maxNearbyEntities = 6; + public int requiredPlayerRange = 16; + public int spawnRange = 4; ++ private int tickDelay = 0; // Paper + + public MobSpawnerAbstract() {} + +@@ -70,6 +71,10 @@ public abstract class MobSpawnerAbstract { + } + + public void c() { ++ // Paper start - Configurable mob spawner tick rate ++ if (spawnDelay > 0 && --tickDelay > 0) return; ++ tickDelay = this.a().paperConfig.mobSpawnerTickRate; ++ // Paper end + if (!this.h()) { + this.f = this.e; + } else { +@@ -84,18 +89,18 @@ public abstract class MobSpawnerAbstract { + world.addParticle(Particles.SMOKE, d0, d1, d2, 0.0D, 0.0D, 0.0D); + world.addParticle(Particles.FLAME, d0, d1, d2, 0.0D, 0.0D, 0.0D); + if (this.spawnDelay > 0) { +- --this.spawnDelay; ++ this.spawnDelay -= tickDelay; // Paper + } + + this.f = this.e; + this.e = (this.e + (double) (1000.0F / ((float) this.spawnDelay + 200.0F))) % 360.0D; + } else { +- if (this.spawnDelay == -1) { ++ if (this.spawnDelay < -tickDelay) { // Paper + this.i(); + } + + if (this.spawnDelay > 0) { +- --this.spawnDelay; ++ this.spawnDelay -= tickDelay; // Paper + return; + } + diff --git a/patches/server-unmapped/0001/0039-Send-absolute-position-the-first-time-an-entity-is-s.patch b/patches/server-unmapped/0001/0039-Send-absolute-position-the-first-time-an-entity-is-s.patch new file mode 100644 index 0000000000..aa467e3d98 --- /dev/null +++ b/patches/server-unmapped/0001/0039-Send-absolute-position-the-first-time-an-entity-is-s.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Wed, 2 Mar 2016 23:13:07 -0600 +Subject: [PATCH] Send absolute position the first time an entity is seen + + +diff --git a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java +index 9ad74b380a92e3a563e1a891e81401d8b4707bcf..beb0beb716869978be6bc5a78ce3b6cf785c5aee 100644 +--- a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java ++++ b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java +@@ -4,6 +4,7 @@ import com.google.common.collect.Lists; + import com.mojang.datafixers.util.Pair; + import java.util.Collection; + import java.util.Collections; ++import java.util.HashSet; + import java.util.Iterator; + import java.util.List; + import java.util.Set; +@@ -52,7 +53,7 @@ public class EntityTrackerEntry { + private final Entity tracker; + private final int d; + private final boolean e; +- private final Consumer> f; ++ private final Consumer> f; private Consumer> getPacketConsumer() { return f; } // Paper - OBFHELPER + private long xLoc; + private long yLoc; + private long zLoc; +@@ -67,8 +68,23 @@ public class EntityTrackerEntry { + private boolean r; + // CraftBukkit start + private final Set trackedPlayers; ++ // Paper start ++ private java.util.Map trackedPlayerMap = null; ++ ++ /** ++ * Requested in https://github.com/PaperMC/Paper/issues/1537 to allow intercepting packets ++ */ ++ public void sendPlayerPacket(EntityPlayer player, Packet packet) { ++ player.playerConnection.sendPacket(packet); ++ } ++ ++ public EntityTrackerEntry(WorldServer worldserver, Entity entity, int i, boolean flag, Consumer> consumer, java.util.Map trackedPlayers) { ++ this(worldserver, entity, i, flag, consumer, trackedPlayers.keySet()); ++ trackedPlayerMap = trackedPlayers; ++ } + + public EntityTrackerEntry(WorldServer worldserver, Entity entity, int i, boolean flag, Consumer> consumer, Set trackedPlayers) { ++ // Paper end + this.trackedPlayers = trackedPlayers; + // CraftBukkit end + this.m = Vec3D.ORIGIN; +@@ -189,7 +205,25 @@ public class EntityTrackerEntry { + } + + if (packet1 != null) { +- this.f.accept(packet1); ++ // paper start ++ if (trackedPlayerMap == null || packet1 instanceof PacketPlayOutEntityTeleport) { ++ this.f.accept((packet1)); ++ } else { ++ PacketPlayOutEntityTeleport teleportPacket = null; ++ ++ for (java.util.Map.Entry viewer : trackedPlayerMap.entrySet()) { ++ if (viewer.getValue()) { ++ viewer.setValue(false); ++ if (teleportPacket == null) { ++ teleportPacket = new PacketPlayOutEntityTeleport(this.tracker); ++ } ++ sendPlayerPacket(viewer.getKey(), teleportPacket); ++ } else { ++ sendPlayerPacket(viewer.getKey(), packet1); ++ } ++ } ++ } ++ // Paper end + } + + this.c(); +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 9eae9d7e9d18d73b1050e1d9b8859802cbd286ed..e9657faf0a24aee8444372e6f1ca0d971339ce5a 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -1295,10 +1295,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + private final Entity tracker; + private final int trackingDistance; + private SectionPosition e; +- public final Set trackedPlayers = Sets.newHashSet(); ++ // Paper start ++ // Replace trackedPlayers Set with a Map. The value is true until the player receives ++ // their first update (which is forced to have absolute coordinates), false afterward. ++ public java.util.Map trackedPlayerMap = new java.util.HashMap<>(); ++ public Set trackedPlayers = trackedPlayerMap.keySet(); + + public EntityTracker(Entity entity, int i, int j, boolean flag) { +- this.trackerEntry = new EntityTrackerEntry(PlayerChunkMap.this.world, entity, j, flag, this::broadcast, trackedPlayers); // CraftBukkit ++ this.trackerEntry = new EntityTrackerEntry(PlayerChunkMap.this.world, entity, j, flag, this::broadcast, trackedPlayerMap); // CraftBukkit // Paper + this.tracker = entity; + this.trackingDistance = i; + this.e = SectionPosition.a(entity); +@@ -1380,7 +1384,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + entityplayer.removeQueue.remove(Integer.valueOf(this.tracker.getId())); + // CraftBukkit end + +- if (flag1 && this.trackedPlayers.add(entityplayer)) { ++ if (flag1 && this.trackedPlayerMap.putIfAbsent(entityplayer, true) == null) { // Paper + this.trackerEntry.b(entityplayer); + } + } else if (this.trackedPlayers.remove(entityplayer)) { diff --git a/patches/server-unmapped/0001/0040-Add-BeaconEffectEvent.patch b/patches/server-unmapped/0001/0040-Add-BeaconEffectEvent.patch new file mode 100644 index 0000000000..a2c0b8f5b8 --- /dev/null +++ b/patches/server-unmapped/0001/0040-Add-BeaconEffectEvent.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Wed, 2 Mar 2016 23:30:53 -0600 +Subject: [PATCH] Add BeaconEffectEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeacon.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeacon.java +index c6914c8d2a3d1057c98537a3538097d3ac6149e0..f9b1ab0e19ff398a16b1452e86f1a165a4b54219 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeacon.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeacon.java +@@ -42,6 +42,11 @@ import net.minecraft.world.phys.AxisAlignedBB; + import org.bukkit.craftbukkit.potion.CraftPotionUtil; + import org.bukkit.potion.PotionEffect; + // CraftBukkit end ++// Paper start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.entity.Player; ++import com.destroystokyo.paper.event.block.BeaconEffectEvent; ++// Paper end + + public class TileEntityBeacon extends TileEntity implements ITileInventory, ITickable { + +@@ -268,14 +273,31 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic + } + + private void applyEffect(List list, MobEffectList effects, int i, int b0) { ++ // Paper - BeaconEffectEvent ++ applyEffect(list, effects, i, b0, true); ++ } ++ ++ private void applyEffect(List list, MobEffectList effects, int i, int b0, boolean isPrimary) { ++ // Paper - BeaconEffectEvent + { + Iterator iterator = list.iterator(); + + EntityHuman entityhuman; + ++ // Paper start - BeaconEffectEvent ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()); ++ PotionEffect effect = CraftPotionUtil.toBukkit(new MobEffect(effects, i, b0, true, true)); ++ // Paper end ++ + while (iterator.hasNext()) { + entityhuman = (EntityHuman) iterator.next(); +- entityhuman.addEffect(new MobEffect(effects, i, b0, true, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.BEACON); ++ ++ // Paper start - BeaconEffectEvent ++ BeaconEffectEvent event = new BeaconEffectEvent(block, effect, (Player) entityhuman.getBukkitEntity(), isPrimary); ++ if (CraftEventFactory.callEvent(event).isCancelled()) continue; ++ PotionEffect eventEffect = event.getEffect(); ++ entityhuman.addEffect(new MobEffect(MobEffectList.fromId(eventEffect.getType().getId()), eventEffect.getDuration(), eventEffect.getAmplifier(), true, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.BEACON); ++ // Paper end + } + } + } +@@ -298,10 +320,10 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic + int i = getLevel(); + List list = getHumansInRange(); + +- applyEffect(list, this.primaryEffect, i, b0); ++ applyEffect(list, this.primaryEffect, i, b0, true); // Paper - BeaconEffectEvent + + if (hasSecondaryEffect()) { +- applyEffect(list, this.secondaryEffect, i, 0); ++ applyEffect(list, this.secondaryEffect, i, 0, false); // Paper - BeaconEffectEvent + } + } + diff --git a/patches/server-unmapped/0001/0041-Configurable-container-update-tick-rate.patch b/patches/server-unmapped/0001/0041-Configurable-container-update-tick-rate.patch new file mode 100644 index 0000000000..9a8704cf40 --- /dev/null +++ b/patches/server-unmapped/0001/0041-Configurable-container-update-tick-rate.patch @@ -0,0 +1,46 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 4de86b09c6bc3c1974ce61b550ccb73d37f6f170..5a4c3a8c511f22c8c3240c9c7cd83a65119c1054 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -181,4 +181,9 @@ public class PaperWorldConfig { + private void mobSpawnerTickRate() { + mobSpawnerTickRate = getInt("mob-spawner-tick-rate", 1); + } ++ ++ public int containerUpdateTickRate; ++ private void containerUpdateTickRate() { ++ containerUpdateTickRate = getInt("container-update-tick-rate", 1); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 68c3cde85f9b40c8638146dde0045822f0b123db..ad55212370e3d814a397680927a1514ea0fe85b5 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -209,6 +209,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public boolean e; + public int ping; + public boolean viewingCredits; ++ private int containerUpdateDelay; // Paper + + // CraftBukkit start + public String displayName; +@@ -533,7 +534,12 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + --this.noDamageTicks; + } + +- this.activeContainer.c(); ++ // Paper start - Configurable container update tick rate ++ if (--containerUpdateDelay <= 0) { ++ this.activeContainer.c(); ++ containerUpdateDelay = world.paperConfig.containerUpdateTickRate; ++ } ++ // Paper end + if (!this.world.isClientSide && !this.activeContainer.canUse(this)) { + this.closeInventory(); + this.activeContainer = this.defaultContainer; diff --git a/patches/server-unmapped/0001/0042-Use-UserCache-for-player-heads.patch b/patches/server-unmapped/0001/0042-Use-UserCache-for-player-heads.patch new file mode 100644 index 0000000000..1def3bad81 --- /dev/null +++ b/patches/server-unmapped/0001/0042-Use-UserCache-for-player-heads.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Techcable +Date: Wed, 2 Mar 2016 23:42:37 -0600 +Subject: [PATCH] Use UserCache for player heads + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +index b8cac55c9fdcebe7703f179d25ad8cfb15c78a0e..dff67a48961399f3746f99b4f2363724bfe51c36 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +@@ -166,7 +166,13 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + if (name == null) { + setProfile(null); + } else { +- setProfile(new GameProfile(null, name)); ++ // Paper start - Use Online Players Skull ++ GameProfile newProfile = null; ++ net.minecraft.server.EntityPlayer player = net.minecraft.server.MinecraftServer.getServer().getPlayerList().getPlayer(name); ++ if (player != null) newProfile = player.getProfile(); ++ if (newProfile == null) newProfile = new GameProfile(null, name); ++ setProfile(newProfile); ++ // Paper end + } + + return true; diff --git a/patches/server-unmapped/0001/0043-Disable-spigot-tick-limiters.patch b/patches/server-unmapped/0001/0043-Disable-spigot-tick-limiters.patch new file mode 100644 index 0000000000..f004850d40 --- /dev/null +++ b/patches/server-unmapped/0001/0043-Disable-spigot-tick-limiters.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 2 Mar 2016 23:45:17 -0600 +Subject: [PATCH] Disable spigot tick limiters + + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 606e54d49875eb2da4cc5b2857b8727bb6469f74..b5594b31366149a02dcb97247bf95a69ca4e4c18 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -707,9 +707,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + // Spigot start + // Iterator iterator = this.tileEntityListTick.iterator(); + int tilesThisCycle = 0; +- for (tileLimiter.initTick(); +- tilesThisCycle < tileEntityListTick.size() && (tilesThisCycle % 10 != 0 || tileLimiter.shouldContinue()); +- tileTickPosition++, tilesThisCycle++) { ++ for (tileTickPosition = 0; tileTickPosition < tileEntityListTick.size(); tileTickPosition++) { // Paper - Disable tick limiters + tileTickPosition = (tileTickPosition < tileEntityListTick.size()) ? tileTickPosition : 0; + TileEntity tileentity = (TileEntity) this.tileEntityListTick.get(tileTickPosition); + // Spigot start diff --git a/patches/server-unmapped/0001/0044-Add-PlayerInitialSpawnEvent.patch b/patches/server-unmapped/0001/0044-Add-PlayerInitialSpawnEvent.patch new file mode 100644 index 0000000000..48a6f05244 --- /dev/null +++ b/patches/server-unmapped/0001/0044-Add-PlayerInitialSpawnEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Steve Anton +Date: Thu, 3 Mar 2016 00:09:38 -0600 +Subject: [PATCH] Add PlayerInitialSpawnEvent + +For modifying a player's initial spawn location as they join the server + +This is a duplicate API from spigot, so use our duplicate subclass and +improve setPosition to use raw + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 15418868c2b92498139e66d913ee1c35b3abf0cf..cfd0af520dd3dcf364a3ffd03a74e3b9ee6045af 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -213,7 +213,7 @@ public abstract class PlayerList { + + // Spigot start - spawn location event + Player bukkitPlayer = entityplayer.getBukkitEntity(); +- org.spigotmc.event.player.PlayerSpawnLocationEvent ev = new org.spigotmc.event.player.PlayerSpawnLocationEvent(bukkitPlayer, bukkitPlayer.getLocation()); ++ org.spigotmc.event.player.PlayerSpawnLocationEvent ev = new com.destroystokyo.paper.event.player.PlayerInitialSpawnEvent(bukkitPlayer, bukkitPlayer.getLocation()); // Paper use our duplicate event + cserver.getPluginManager().callEvent(ev); + + Location loc = ev.getSpawnLocation(); +@@ -221,7 +221,10 @@ public abstract class PlayerList { + + entityplayer.spawnIn(worldserver1); + entityplayer.playerInteractManager.a((WorldServer) entityplayer.world); +- entityplayer.setLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); ++ // Paper start - set raw so we aren't fully joined to the world (not added to chunk or world) ++ entityplayer.setPositionRaw(loc.getX(), loc.getY(), loc.getZ()); ++ entityplayer.setYawPitch(loc.getYaw(), loc.getPitch()); ++ // Paper end + // Spigot end + + // CraftBukkit - Moved message to after join +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 75734c1db3baecfa5b89c74335a537c3f7a07c2e..7a73596798b4a57fafa1c1833abacc559d415906 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -398,7 +398,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return d1 * d1 + d2 * d2 + d3 * d3 < d0 * d0; + } + +- protected void setYawPitch(float f, float f1) { ++ public void setYawPitch(float f, float f1) { // Paper - protected -> public + // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0 + if (Float.isNaN(f)) { + f = 0; diff --git a/patches/server-unmapped/0001/0045-Configurable-Disabling-Cat-Chest-Detection.patch b/patches/server-unmapped/0001/0045-Configurable-Disabling-Cat-Chest-Detection.patch new file mode 100644 index 0000000000..9c34c4ef30 --- /dev/null +++ b/patches/server-unmapped/0001/0045-Configurable-Disabling-Cat-Chest-Detection.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 01:13:45 -0600 +Subject: [PATCH] Configurable Disabling Cat Chest Detection + +Offers a gameplay feature to stop cats from blocking chests + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 5a4c3a8c511f22c8c3240c9c7cd83a65119c1054..70e074cdf2087e638af8e0f3878d0ef8eb7305cc 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -186,4 +186,9 @@ public class PaperWorldConfig { + private void containerUpdateTickRate() { + containerUpdateTickRate = getInt("container-update-tick-rate", 1); + } ++ ++ public boolean disableChestCatDetection; ++ private void disableChestCatDetection() { ++ disableChestCatDetection = getBoolean("game-mechanics.disable-chest-cat-detection", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/BlockChest.java b/src/main/java/net/minecraft/world/level/block/BlockChest.java +index b229faad99120c67b089f7680d800fbe594fe7da..b2c29cff5883868cb56a4e376ab946ac929abc94 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockChest.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockChest.java +@@ -312,6 +312,11 @@ public class BlockChest extends BlockChestAbstract implements I + } + + private static boolean b(GeneratorAccess generatoraccess, BlockPosition blockposition) { ++ // Paper start - Option to disable chest cat detection ++ if (((World) generatoraccess).paperConfig.disableChestCatDetection) { ++ return false; ++ } ++ // Paper end + List list = generatoraccess.a(EntityCat.class, new AxisAlignedBB((double) blockposition.getX(), (double) (blockposition.getY() + 1), (double) blockposition.getZ(), (double) (blockposition.getX() + 1), (double) (blockposition.getY() + 2), (double) (blockposition.getZ() + 1))); + + if (!list.isEmpty()) { diff --git a/patches/server-unmapped/0001/0046-Ensure-commands-are-not-ran-async.patch b/patches/server-unmapped/0001/0046-Ensure-commands-are-not-ran-async.patch new file mode 100644 index 0000000000..f254df9967 --- /dev/null +++ b/patches/server-unmapped/0001/0046-Ensure-commands-are-not-ran-async.patch @@ -0,0 +1,119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 01:17:12 -0600 +Subject: [PATCH] Ensure commands are not ran async + +Plugins calling Player.chat("/foo") or Server.dispatchCommand() could +trigger the server to execute a command while on another thread. + +These commands would then process EXPECTING to be on the main thread, leaving to +very hard to trace concurrency issues. + +This change will synchronize the command execution back to the main thread, causing a +big slowdown in execution but throwing an exception at same time to raise awareness +that it is happening so that plugin authors can fix their code to stop executing commands async. + +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index a39f58e0c60b5e3ccc3b725f1f4167d52b230e11..6a8567c355202560ee523c6dc68cac1ac3e562fd 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1853,6 +1853,29 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + + if (!async && s.startsWith("/")) { ++ // Paper Start ++ if (!org.spigotmc.AsyncCatcher.shuttingDown && !org.bukkit.Bukkit.isPrimaryThread()) { ++ final String fCommandLine = s; ++ MinecraftServer.LOGGER.log(org.apache.logging.log4j.Level.ERROR, "Command Dispatched Async: " + fCommandLine); ++ MinecraftServer.LOGGER.log(org.apache.logging.log4j.Level.ERROR, "Please notify author of plugin causing this execution to fix this bug! see: http://bit.ly/1oSiM6C", new Throwable()); ++ Waitable wait = new Waitable() { ++ @Override ++ protected Object evaluate() { ++ chat(fCommandLine, false); ++ return null; ++ } ++ }; ++ minecraftServer.processQueue.add(wait); ++ try { ++ wait.get(); ++ return; ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on! ++ } catch (Exception e) { ++ throw new RuntimeException("Exception processing chat command", e.getCause()); ++ } ++ } ++ // Paper End + this.handleCommand(s); + } else if (this.player.getChatFlags() == EnumChatVisibility.SYSTEM) { + // Do nothing, this is coming from a plugin +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f84fe5929cb7bcedff5fc587163380172bc1e8be..8e9547fc745e8244ca1f439770571ca1175ff632 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -762,6 +762,29 @@ public final class CraftServer implements Server { + Validate.notNull(commandLine, "CommandLine cannot be null"); + org.spigotmc.AsyncCatcher.catchOp("command dispatch"); // Spigot + ++ // Paper Start ++ if (!org.spigotmc.AsyncCatcher.shuttingDown && !Bukkit.isPrimaryThread()) { ++ final CommandSender fSender = sender; ++ final String fCommandLine = commandLine; ++ Bukkit.getLogger().log(Level.SEVERE, "Command Dispatched Async: " + commandLine); ++ Bukkit.getLogger().log(Level.SEVERE, "Please notify author of plugin causing this execution to fix this bug! see: http://bit.ly/1oSiM6C", new Throwable()); ++ org.bukkit.craftbukkit.util.Waitable wait = new org.bukkit.craftbukkit.util.Waitable() { ++ @Override ++ protected Boolean evaluate() { ++ return dispatchCommand(fSender, fCommandLine); ++ } ++ }; ++ net.minecraft.server.MinecraftServer.getServer().processQueue.add(wait); ++ try { ++ return wait.get(); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on! ++ } catch (Exception e) { ++ throw new RuntimeException("Exception processing dispatch command", e.getCause()); ++ } ++ } ++ // Paper End ++ + if (commandMap.dispatch(sender, commandLine)) { + return true; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +index ddef523ea8762c927f37f7d16d581e43367e8c6b..70f8d42992aa348ef7b2d03d22cdd59d7c73f0fe 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +@@ -13,6 +13,7 @@ public class ServerShutdownThread extends Thread { + public void run() { + try { + org.spigotmc.AsyncCatcher.enabled = false; // Spigot ++ org.spigotmc.AsyncCatcher.shuttingDown = true; // Paper + server.close(); + } finally { + try { +diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java +index aeed7697254af17ffefe8e578353ad216e15f9f3..9f7d2ef932ab41cef5d3d0736d20a7c7e4a2c888 100644 +--- a/src/main/java/org/spigotmc/AsyncCatcher.java ++++ b/src/main/java/org/spigotmc/AsyncCatcher.java +@@ -6,6 +6,7 @@ public class AsyncCatcher + { + + public static boolean enabled = true; ++ public static boolean shuttingDown = false; // Paper + + public static void catchOp(String reason) + { +diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java +index d76e01d73da7413f192132134caf201d7780e3f1..a45155cdd7d7a302c119f75bfe2b428ae5e8ab47 100644 +--- a/src/main/java/org/spigotmc/RestartCommand.java ++++ b/src/main/java/org/spigotmc/RestartCommand.java +@@ -43,6 +43,7 @@ public class RestartCommand extends Command + private static void restart(final String restartScript) + { + AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us ++ org.spigotmc.AsyncCatcher.shuttingDown = true; // Paper + try + { + String[] split = restartScript.split( " " ); diff --git a/patches/server-unmapped/0001/0047-All-chunks-are-slime-spawn-chunks-toggle.patch b/patches/server-unmapped/0001/0047-All-chunks-are-slime-spawn-chunks-toggle.patch new file mode 100644 index 0000000000..48622f89ba --- /dev/null +++ b/patches/server-unmapped/0001/0047-All-chunks-are-slime-spawn-chunks-toggle.patch @@ -0,0 +1,33 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 70e074cdf2087e638af8e0f3878d0ef8eb7305cc..416a6760883cb40367535c7c5acd779742bb8af5 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -191,4 +191,9 @@ public class PaperWorldConfig { + private void disableChestCatDetection() { + disableChestCatDetection = getBoolean("game-mechanics.disable-chest-cat-detection", false); + } ++ ++ public boolean allChunksAreSlimeChunks; ++ private void allChunksAreSlimeChunks() { ++ allChunksAreSlimeChunks = getBoolean("all-chunks-are-slime-chunks", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java b/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java +index 292789d76da400d15d0742e2e0979f4ac6ec4b75..01d5b0db9a34d88172e8c7c84c4e1d0b2562217c 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java +@@ -325,7 +325,7 @@ public class EntitySlime extends EntityInsentient implements IMonster { + } + + ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(blockposition); +- boolean flag = SeededRandom.a(chunkcoordintpair.x, chunkcoordintpair.z, ((GeneratorAccessSeed) generatoraccess).getSeed(), generatoraccess.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot ++ boolean flag = generatoraccess.getMinecraftWorld().paperConfig.allChunksAreSlimeChunks || SeededRandom.a(chunkcoordintpair.x, chunkcoordintpair.z, ((GeneratorAccessSeed) generatoraccess).getSeed(), generatoraccess.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper + + if (random.nextInt(10) == 0 && flag && blockposition.getY() < 40) { + return a(entitytypes, generatoraccess, enummobspawn, blockposition, random); diff --git a/patches/server-unmapped/0001/0048-Expose-server-CommandMap.patch b/patches/server-unmapped/0001/0048-Expose-server-CommandMap.patch new file mode 100644 index 0000000000..d3743c97bc --- /dev/null +++ b/patches/server-unmapped/0001/0048-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 8e9547fc745e8244ca1f439770571ca1175ff632..96d4049f8a42f00dbbc092b391ae11c1326a5501 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1752,6 +1752,7 @@ public final class CraftServer implements Server { + return helpMap; + } + ++ @Override // Paper - add override + public SimpleCommandMap getCommandMap() { + return commandMap; + } diff --git a/patches/server-unmapped/0001/0049-Be-a-bit-more-informative-in-maxHealth-exception.patch b/patches/server-unmapped/0001/0049-Be-a-bit-more-informative-in-maxHealth-exception.patch new file mode 100644 index 0000000000..6f72badc91 --- /dev/null +++ b/patches/server-unmapped/0001/0049-Be-a-bit-more-informative-in-maxHealth-exception.patch @@ -0,0 +1,22 @@ +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 da0a7a60cbabe0dafa9630b8dcba98e64dcc8d3a..d863fc9fa6b932b76a89871a09378a9c0697c108 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -111,7 +111,10 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setHealth(double health) { + health = (float) health; + if ((health < 0) || (health > getMaxHealth())) { +- throw new IllegalArgumentException("Health must be between 0 and " + getMaxHealth() + "(" + health + ")"); ++ // Paper - Be more informative ++ throw new IllegalArgumentException("Health must be between 0 and " + getMaxHealth() + ", but was " + health ++ + ". (attribute base value: " + this.getHandle().getAttributeInstance(GenericAttributes.MAX_HEALTH).getBaseValue() ++ + (this instanceof CraftPlayer ? ", player: " + this.getName() + ')' : ')')); + } + + getHandle().setHealth((float) health); diff --git a/patches/server-unmapped/0001/0050-Player-Tab-List-and-Title-APIs.patch b/patches/server-unmapped/0001/0050-Player-Tab-List-and-Title-APIs.patch new file mode 100644 index 0000000000..25584dbe13 --- /dev/null +++ b/patches/server-unmapped/0001/0050-Player-Tab-List-and-Title-APIs.patch @@ -0,0 +1,173 @@ +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/PacketDataSerializer.java b/src/main/java/net/minecraft/network/PacketDataSerializer.java +index 5f1c5dd7902f6cff5acae05e8c6bf58a1ba5bdf1..df459918c14589155a574730205cb35d463b8079 100644 +--- a/src/main/java/net/minecraft/network/PacketDataSerializer.java ++++ b/src/main/java/net/minecraft/network/PacketDataSerializer.java +@@ -171,6 +171,11 @@ public class PacketDataSerializer extends ByteBuf { + public PacketDataSerializer writeComponent(final net.kyori.adventure.text.Component component) { + return this.writeUtf(PaperAdventure.asJsonString(component, this.adventure$locale), 262144); + } ++ ++ @Deprecated ++ public PacketDataSerializer writeComponent(final net.md_5.bungee.api.chat.BaseComponent[] component) { ++ return this.writeUtf(net.md_5.bungee.chat.ComponentSerializer.toString(component), 262144); ++ } + // Paper end + + public PacketDataSerializer a(IChatBaseComponent ichatbasecomponent) { +diff --git a/src/main/java/net/minecraft/network/chat/IChatBaseComponent.java b/src/main/java/net/minecraft/network/chat/IChatBaseComponent.java +index c7c191b0a9889450fdf495f5aa45d59f159f1401..e0fe2ba95bd3cb6afcf9c804007438a513c095b7 100644 +--- a/src/main/java/net/minecraft/network/chat/IChatBaseComponent.java ++++ b/src/main/java/net/minecraft/network/chat/IChatBaseComponent.java +@@ -363,6 +363,7 @@ public interface IChatBaseComponent extends Message, IChatFormatted, Iterable { + } + + } ++ // Paper start ++ public net.md_5.bungee.api.chat.BaseComponent[] components; ++ ++ public PacketPlayOutTitle(EnumTitleAction action, net.md_5.bungee.api.chat.BaseComponent[] components, int fadeIn, int stay, int fadeOut) { ++ this.a = action; ++ this.components = components; ++ this.c = fadeIn; ++ this.d = stay; ++ this.e = fadeOut; ++ } ++ // Paper end + + @Override + public void b(PacketDataSerializer packetdataserializer) throws IOException { +@@ -55,6 +66,8 @@ public class PacketPlayOutTitle implements Packet { + // Paper start + if (this.adventure$text != null) { + packetdataserializer.writeComponent(this.adventure$text); ++ } else if (this.components != null) { ++ packetdataserializer.writeComponent(this.components); + } else + // Paper end + packetdataserializer.a(this.b); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 62f8d96f996ece87b7ab8d5d05d1dc214d10dbfa..9837f7364f3efd0aa22d33058bec369c41cd03ef 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit.entity; + ++import com.destroystokyo.paper.Title; + import com.google.common.base.Preconditions; + import com.google.common.collect.ImmutableSet; + import com.google.common.io.BaseEncoding; +@@ -238,6 +239,96 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + } + ++ // Paper start ++ @Override ++ public void setPlayerListHeaderFooter(BaseComponent[] header, BaseComponent[] footer) { ++ if (header != null) { ++ String headerJson = net.md_5.bungee.chat.ComponentSerializer.toString(header); ++ playerListHeader = net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(headerJson); ++ } else { ++ playerListHeader = null; ++ } ++ ++ if (footer != null) { ++ String footerJson = net.md_5.bungee.chat.ComponentSerializer.toString(footer); ++ playerListFooter = net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(footerJson); ++ } else { ++ playerListFooter = null; ++ } ++ ++ updatePlayerListHeaderFooter(); ++ } ++ ++ @Override ++ public void setPlayerListHeaderFooter(BaseComponent header, BaseComponent footer) { ++ this.setPlayerListHeaderFooter(header == null ? null : new BaseComponent[]{header}, ++ footer == null ? null : new BaseComponent[]{footer}); ++ } ++ ++ ++ @Override ++ public void setTitleTimes(int fadeInTicks, int stayTicks, int fadeOutTicks) { ++ getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.TIMES, (BaseComponent[]) null, fadeInTicks, stayTicks, fadeOutTicks)); ++ } ++ ++ @Override ++ public void setSubtitle(BaseComponent[] subtitle) { ++ getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.SUBTITLE, subtitle, 0, 0, 0)); ++ } ++ ++ @Override ++ public void setSubtitle(BaseComponent subtitle) { ++ setSubtitle(new BaseComponent[]{subtitle}); ++ } ++ ++ @Override ++ public void showTitle(BaseComponent[] title) { ++ getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.TITLE, title, 0, 0, 0)); ++ } ++ ++ @Override ++ public void showTitle(BaseComponent title) { ++ showTitle(new BaseComponent[]{title}); ++ } ++ ++ @Override ++ public void showTitle(BaseComponent[] title, BaseComponent[] subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) { ++ setTitleTimes(fadeInTicks, stayTicks, fadeOutTicks); ++ setSubtitle(subtitle); ++ showTitle(title); ++ } ++ ++ @Override ++ public void showTitle(BaseComponent title, BaseComponent subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) { ++ setTitleTimes(fadeInTicks, stayTicks, fadeOutTicks); ++ setSubtitle(subtitle); ++ showTitle(title); ++ } ++ ++ @Override ++ public void sendTitle(Title title) { ++ Preconditions.checkNotNull(title, "Title is null"); ++ setTitleTimes(title.getFadeIn(), title.getStay(), title.getFadeOut()); ++ setSubtitle(title.getSubtitle() == null ? new BaseComponent[0] : title.getSubtitle()); ++ showTitle(title.getTitle()); ++ } ++ ++ @Override ++ public void updateTitle(Title title) { ++ Preconditions.checkNotNull(title, "Title is null"); ++ setTitleTimes(title.getFadeIn(), title.getStay(), title.getFadeOut()); ++ if (title.getSubtitle() != null) { ++ setSubtitle(title.getSubtitle()); ++ } ++ showTitle(title.getTitle()); ++ } ++ ++ @Override ++ public void hideTitle() { ++ getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.CLEAR, (BaseComponent[]) null, 0, 0, 0)); ++ } ++ // Paper end ++ + @Override + public String getDisplayName() { + if(true) return io.papermc.paper.adventure.DisplayNames.getLegacy(this); // Paper diff --git a/patches/server-unmapped/0001/0051-Ensure-inv-drag-is-in-bounds.patch b/patches/server-unmapped/0001/0051-Ensure-inv-drag-is-in-bounds.patch new file mode 100644 index 0000000000..3d814228e9 --- /dev/null +++ b/patches/server-unmapped/0001/0051-Ensure-inv-drag-is-in-bounds.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Joseph Hirschfeld +Date: Thu, 3 Mar 2016 02:33:53 -0600 +Subject: [PATCH] Ensure inv drag is in bounds + + +diff --git a/src/main/java/net/minecraft/world/inventory/Container.java b/src/main/java/net/minecraft/world/inventory/Container.java +index 2410214a300407ef20ea14244db5db2ebede2759..e9733fd9dac89d31dbad391cb22a8c84216045db 100644 +--- a/src/main/java/net/minecraft/world/inventory/Container.java ++++ b/src/main/java/net/minecraft/world/inventory/Container.java +@@ -239,7 +239,7 @@ public abstract class Container { + this.d(); + } + } else if (this.h == 1) { +- Slot slot = (Slot) this.slots.get(i); ++ Slot slot = i < this.slots.size() ? this.slots.get(i) : null; // Paper - Ensure drag in bounds + + itemstack1 = playerinventory.getCarried(); + if (slot != null && a(slot, itemstack1, true) && slot.isAllowed(itemstack1) && (this.dragType == 2 || itemstack1.getCount() > this.i.size()) && this.b(slot)) { diff --git a/patches/server-unmapped/0001/0052-Change-implementation-of-tile-entity-removal-list.patch b/patches/server-unmapped/0001/0052-Change-implementation-of-tile-entity-removal-list.patch new file mode 100644 index 0000000000..71f39c8a86 --- /dev/null +++ b/patches/server-unmapped/0001/0052-Change-implementation-of-tile-entity-removal-list.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Joseph Hirschfeld +Date: Thu, 3 Mar 2016 02:39:54 -0600 +Subject: [PATCH] Change implementation of (tile)entity removal list + +use sets for faster removal + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index b5594b31366149a02dcb97247bf95a69ca4e4c18..805a0aae86c9c8e2c1246cbfee9e73684a6b7e6b 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -89,7 +89,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public final List tileEntityList = Lists.newArrayList(); + public final List tileEntityListTick = Lists.newArrayList(); + protected final List tileEntityListPending = Lists.newArrayList(); +- protected final List tileEntityListUnload = Lists.newArrayList(); ++ protected final java.util.Set tileEntityListUnload = com.google.common.collect.Sets.newHashSet(); + public final Thread serverThread; + private final boolean debugWorld; + private int d; diff --git a/patches/server-unmapped/0001/0053-Add-configurable-portal-search-radius.patch b/patches/server-unmapped/0001/0053-Add-configurable-portal-search-radius.patch new file mode 100644 index 0000000000..70e2152f15 --- /dev/null +++ b/patches/server-unmapped/0001/0053-Add-configurable-portal-search-radius.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Joseph Hirschfeld +Date: Thu, 3 Mar 2016 02:46:17 -0600 +Subject: [PATCH] Add configurable portal search radius + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 416a6760883cb40367535c7c5acd779742bb8af5..670efbe53241a0ae32d618c83da601ccc1f26e37 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -196,4 +196,13 @@ public class PaperWorldConfig { + private void allChunksAreSlimeChunks() { + allChunksAreSlimeChunks = getBoolean("all-chunks-are-slime-chunks", false); + } ++ ++ public int portalSearchRadius; ++ public int portalCreateRadius; ++ public boolean portalSearchVanillaDimensionScaling; ++ private void portalSearchRadius() { ++ portalSearchRadius = getInt("portal-search-radius", 128); ++ portalCreateRadius = getInt("portal-create-radius", 16); ++ portalSearchVanillaDimensionScaling = getBoolean("portal-search-vanilla-dimension-scaling", true); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7a73596798b4a57fafa1c1833abacc559d415906..3c82d592903844b032e3fe4777dcb23be936cfc0 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2613,7 +2613,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + double d4 = DimensionManager.a(this.world.getDimensionManager(), worldserver.getDimensionManager()); + BlockPosition blockposition = new BlockPosition(MathHelper.a(this.locX() * d4, d0, d2), this.locY(), MathHelper.a(this.locZ() * d4, d1, d3)); + // CraftBukkit start +- CraftPortalEvent event = callPortalEvent(this, worldserver, blockposition, PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, flag2 ? 16 : 128, 16); ++ // Paper start ++ int portalSearchRadius = worldserver.paperConfig.portalSearchRadius; ++ if (world.paperConfig.portalSearchVanillaDimensionScaling && flag2) { // == THE_NETHER ++ portalSearchRadius = (int) (portalSearchRadius / worldserver.getDimensionManager().getCoordinateScale()); ++ } ++ // Paper end ++ CraftPortalEvent event = callPortalEvent(this, worldserver, blockposition, PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, portalSearchRadius, worldserver.paperConfig.portalCreateRadius); // Paper start - configurable portal radius + if (event == null) { + return null; + } +diff --git a/src/main/java/net/minecraft/world/level/portal/PortalTravelAgent.java b/src/main/java/net/minecraft/world/level/portal/PortalTravelAgent.java +index 7b8e8a7dae47ecc42a57e3f9444caa2ee5b1ef3b..77dfa7eaf178baa55041a829c9dec4851efeedfc 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalTravelAgent.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalTravelAgent.java +@@ -31,7 +31,7 @@ public class PortalTravelAgent { + + public Optional findPortal(BlockPosition blockposition, boolean flag) { + // CraftBukkit start +- return findPortal(blockposition, flag ? 16 : 128); // Search Radius ++ return findPortal(blockposition, flag ? world.paperConfig.portalCreateRadius : world.paperConfig.portalSearchRadius); // Paper - search Radius + } + + public Optional findPortal(BlockPosition blockposition, int i) { diff --git a/patches/server-unmapped/0001/0054-Add-velocity-warnings.patch b/patches/server-unmapped/0001/0054-Add-velocity-warnings.patch new file mode 100644 index 0000000000..eba37e215b --- /dev/null +++ b/patches/server-unmapped/0001/0054-Add-velocity-warnings.patch @@ -0,0 +1,89 @@ +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 96d4049f8a42f00dbbc092b391ae11c1326a5501..f6a1d52239216984da4a9c87312c6ed085213935 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -262,6 +262,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 220bad90bbb9a90c3f23562bf0fb109fce379682..a58626b1a0160983a738a45c8a1d411eb347e6a2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -424,10 +424,41 @@ 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) && isUnsafeVelocity(velocity)) { ++ CraftServer.excessiveVelEx = new Exception("Excessive velocity set detected: tried to set velocity of entity " + entity.getName() + " id #" + getEntityId() + " to (" + velocity.getX() + "," + velocity.getY() + "," + velocity.getZ() + ")."); ++ } ++ // Paper end ++ + entity.setMot(CraftVector.toNMS(velocity)); + entity.velocityChanged = 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 getHandle().getHeight(); +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 1b3a14784cac8e855633fae6172ad5479ebe9877..69e5054886b5858664fed333aca8c25a76e5cb11 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -80,7 +80,19 @@ public class WatchdogThread extends Thread + log.log( Level.SEVERE, "During the run of the server, a physics stackoverflow was supressed" ); + log.log( Level.SEVERE, "near " + net.minecraft.world.level.World.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 + dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); diff --git a/patches/server-unmapped/0001/0055-Configurable-inter-world-teleportation-safety.patch b/patches/server-unmapped/0001/0055-Configurable-inter-world-teleportation-safety.patch new file mode 100644 index 0000000000..82d2161029 --- /dev/null +++ b/patches/server-unmapped/0001/0055-Configurable-inter-world-teleportation-safety.patch @@ -0,0 +1,44 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 670efbe53241a0ae32d618c83da601ccc1f26e37..abbbe1786eb68af02f9d39650aad730ac44aac8a 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -205,4 +205,9 @@ public class PaperWorldConfig { + portalCreateRadius = getInt("portal-create-radius", 16); + portalSearchVanillaDimensionScaling = getBoolean("portal-search-vanilla-dimension-scaling", true); + } ++ ++ public boolean disableTeleportationSuffocationCheck; ++ private void disableTeleportationSuffocationCheck() { ++ disableTeleportationSuffocationCheck = getBoolean("disable-teleportation-suffocation-check", false); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 9837f7364f3efd0aa22d33058bec369c41cd03ef..da193bfc1d98e489eedc373ee87ab58b75d4377e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -865,7 +865,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + if (fromWorld == toWorld) { + entity.playerConnection.teleport(to); + } else { +- server.getHandle().moveToWorld(entity, toWorld, true, to, true); ++ server.getHandle().moveToWorld(entity, toWorld, true, to, !toWorld.paperConfig.disableTeleportationSuffocationCheck); // Paper + } + return true; + } diff --git a/patches/server-unmapped/0001/0056-Add-exception-reporting-event.patch b/patches/server-unmapped/0001/0056-Add-exception-reporting-event.patch new file mode 100644 index 0000000000..5fbf4911b8 --- /dev/null +++ b/patches/server-unmapped/0001/0056-Add-exception-reporting-event.patch @@ -0,0 +1,264 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Joseph Hirschfeld +Date: Thu, 3 Mar 2016 03:15:41 -0600 +Subject: [PATCH] Add exception reporting event + + +diff --git a/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java b/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f699ce18ca044f813e194ef2786b7ea853ea86e7 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java +@@ -0,0 +1,38 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.Preconditions; ++import org.bukkit.craftbukkit.scheduler.CraftTask; ++import com.destroystokyo.paper.event.server.ServerExceptionEvent; ++import com.destroystokyo.paper.exception.ServerSchedulerException; ++ ++/** ++ * Reporting wrapper to catch exceptions not natively ++ */ ++public class ServerSchedulerReportingWrapper implements Runnable { ++ ++ private final CraftTask internalTask; ++ ++ public ServerSchedulerReportingWrapper(CraftTask internalTask) { ++ this.internalTask = Preconditions.checkNotNull(internalTask, "internalTask"); ++ } ++ ++ @Override ++ public void run() { ++ try { ++ internalTask.run(); ++ } catch (RuntimeException e) { ++ internalTask.getOwner().getServer().getPluginManager().callEvent( ++ new ServerExceptionEvent(new ServerSchedulerException(e, internalTask)) ++ ); ++ throw e; ++ } catch (Throwable t) { ++ internalTask.getOwner().getServer().getPluginManager().callEvent( ++ new ServerExceptionEvent(new ServerSchedulerException(t, internalTask)) ++ ); //Do not rethrow, since it is not permitted with Runnable#run ++ } ++ } ++ ++ public CraftTask getInternalTask() { ++ return internalTask; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index e9657faf0a24aee8444372e6f1ca0d971339ce5a..e8150c456efe72a561d6a6a7647eca05fbc8bd94 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -807,6 +807,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return true; + } catch (Exception exception) { + PlayerChunkMap.LOGGER.error("Failed to save chunk {},{}", chunkcoordintpair.x, chunkcoordintpair.z, exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper + return false; + } + } +diff --git a/src/main/java/net/minecraft/server/players/NameReferencingFileConverter.java b/src/main/java/net/minecraft/server/players/NameReferencingFileConverter.java +index 107979178e8be5ee6cf885d42f992fabf3bd00b0..8a343a857dc4661ba256e39cf391dd2c7a1cc970 100644 +--- a/src/main/java/net/minecraft/server/players/NameReferencingFileConverter.java ++++ b/src/main/java/net/minecraft/server/players/NameReferencingFileConverter.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.players; + ++import com.destroystokyo.paper.exception.ServerInternalException; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.io.Files; +@@ -363,6 +364,7 @@ public class NameReferencingFileConverter { + root = NBTCompressedStreamTools.a(new java.io.FileInputStream(file5)); + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + } + + if (root != null) { +@@ -376,6 +378,7 @@ public class NameReferencingFileConverter { + NBTCompressedStreamTools.a(root, new java.io.FileOutputStream(file2)); + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + } + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java b/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java +index 928ca3189af1ddaba797628a087cd6c6a9016f5c..eaa97eb11d893266253fb108249ced1e0e96a4dc 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java +@@ -1,5 +1,7 @@ + package net.minecraft.world.entity.ai.village; + ++import com.destroystokyo.paper.exception.ServerInternalException; ++ + import java.util.Iterator; + import javax.annotation.Nullable; + import net.minecraft.core.BaseBlockPosition; +@@ -119,6 +121,7 @@ public class VillageSiege implements MobSpawner { + entityzombie.prepare(worldserver, worldserver.getDamageScaler(entityzombie.getChunkCoordinates()), EnumMobSpawn.EVENT, (GroupDataEntity) null, (NBTTagCompound) null); + } catch (Exception exception) { + VillageSiege.LOGGER.warn("Failed to create zombie for village siege at {}", vec3d, exception); ++ ServerInternalException.reportInternalException(exception); // Paper + return; + } + +diff --git a/src/main/java/net/minecraft/world/level/SpawnerCreature.java b/src/main/java/net/minecraft/world/level/SpawnerCreature.java +index 2d02b2fc502a0f7e541f7943ed647ff7177acee8..fd0595fd584046326eccacdf0a6afe40c5e84eed 100644 +--- a/src/main/java/net/minecraft/world/level/SpawnerCreature.java ++++ b/src/main/java/net/minecraft/world/level/SpawnerCreature.java +@@ -301,6 +301,7 @@ public final class SpawnerCreature { + } + } catch (Exception exception) { + SpawnerCreature.LOGGER.warn("Failed to create mob", exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper + return null; + } + } +@@ -407,6 +408,7 @@ public final class SpawnerCreature { + entity = biomesettingsmobs_c.c.a((World) worldaccess.getMinecraftWorld()); + } catch (Exception exception) { + SpawnerCreature.LOGGER.warn("Failed to create mob", exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper + continue; + } + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 805a0aae86c9c8e2c1246cbfee9e73684a6b7e6b..b9951b9463c6c171825b212854b26ce639f36f21 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -1,5 +1,10 @@ + package net.minecraft.world.level; + ++import co.aikar.timings.Timing; ++import co.aikar.timings.Timings; ++import com.destroystokyo.paper.event.server.ServerExceptionEvent; ++import com.destroystokyo.paper.exception.ServerInternalException; ++import com.google.common.base.MoreObjects; + import com.google.common.collect.Lists; + import com.mojang.serialization.Codec; + import java.io.IOException; +@@ -737,8 +742,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + gameprofilerfiller.exit(); + } catch (Throwable throwable) { + // Paper start - Prevent tile entity and entity crashes +- System.err.println("TileEntity threw exception at " + tileentity.world.getWorld().getName() + ":" + tileentity.position.getX() + "," + tileentity.position.getY() + "," + tileentity.position.getZ()); ++ String msg = "TileEntity threw exception at " + tileentity.getWorld().getWorld().getName() + ":" + tileentity.getPosition().getX() + "," + tileentity.getPosition().getY() + "," + tileentity.getPosition().getZ(); ++ System.err.println(msg); + throwable.printStackTrace(); ++ getServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable))); ++ // Paper end + tilesThisCycle--; + this.tileEntityListTick.remove(tileTickPosition--); + continue; +@@ -808,8 +816,10 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + consumer.accept(entity); + } catch (Throwable throwable) { + // Paper start - Prevent tile entity and entity crashes +- System.err.println("Entity threw exception at " + entity.world.getWorld().getName() + ":" + entity.locX() + "," + entity.locY() + "," + entity.locZ()); ++ String msg = "Entity threw exception at " + entity.world.getWorld().getName() + ":" + entity.locX() + "," + entity.locY() + "," + entity.locZ(); ++ System.err.println(msg); + throwable.printStackTrace(); ++ getServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable))); + entity.dead = true; + return; + // Paper end +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index a2d80c2c8e4f080f60746548f75631c5946ba8e2..4b3de29b1a6e9d75b28962073c62bbe8d666165f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.level.chunk; + ++import com.destroystokyo.paper.exception.ServerInternalException; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; + import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +@@ -655,10 +656,15 @@ public class Chunk implements IChunkAccess { + this.tileEntities.remove(blockposition); + // Paper end + } else { +- System.out.println("Attempted to place a tile entity (" + tileentity + ") at " + tileentity.getPosition().getX() + "," + tileentity.getPosition().getY() + "," + tileentity.getPosition().getZ() +- + " (" + getType(blockposition) + ") where there was no entity tile!"); +- System.out.println("Chunk coordinates: " + (this.loc.x * 16) + "," + (this.loc.z * 16)); +- new Exception().printStackTrace(); ++ // Paper start ++ ServerInternalException e = new ServerInternalException( ++ "Attempted to place a tile entity (" + tileentity + ") at " + tileentity.getPosition().getX() + "," ++ + tileentity.getPosition().getY() + "," + tileentity.getPosition().getZ() ++ + " (" + getType(blockposition) + ") where there was no entity tile!\n" + ++ "Chunk coordinates: " + (this.loc.x * 16) + "," + (this.loc.z * 16)); ++ e.printStackTrace(); ++ ServerInternalException.reportInternalException(e); ++ // Paper end + // CraftBukkit end + } + } +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 d1b761055c508a4b80436b50a832e00d0449d8cb..1638f7902290e1bb233f11e5d0bbf83a9e863939 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 +@@ -265,6 +265,7 @@ public class RegionFile implements AutoCloseable { + return true; + } + } catch (IOException ioexception) { ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(ioexception); // Paper + return false; + } + } +@@ -337,6 +338,7 @@ public class RegionFile implements AutoCloseable { + filechannel.write(bytebuffer); + } catch (Throwable throwable1) { + throwable = throwable1; ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(throwable); // Paper + throw throwable1; + } finally { + if (filechannel != null) { +diff --git a/src/main/java/net/minecraft/world/level/storage/WorldPersistentData.java b/src/main/java/net/minecraft/world/level/storage/WorldPersistentData.java +index 3910daeaa177639fa8055301304634c2014dc20f..d61960d80599dc5e7b70cc990e4b0b174eb6e34e 100644 +--- a/src/main/java/net/minecraft/world/level/storage/WorldPersistentData.java ++++ b/src/main/java/net/minecraft/world/level/storage/WorldPersistentData.java +@@ -150,6 +150,7 @@ public class WorldPersistentData { + } + } catch (Throwable throwable6) { + throwable = throwable6; ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(throwable); // Paper + throw throwable6; + } finally { + if (fileinputstream != null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index ffe9cc1011226d604dc5499e7692e9a9a5132b72..9b6d9373abb59a30c2835ca891282d07559281f5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -16,6 +16,9 @@ import java.util.concurrent.atomic.AtomicInteger; + import java.util.concurrent.atomic.AtomicReference; + import java.util.function.Consumer; + import java.util.logging.Level; ++import com.destroystokyo.paper.ServerSchedulerReportingWrapper; ++import com.destroystokyo.paper.event.server.ServerExceptionEvent; ++import com.destroystokyo.paper.exception.ServerSchedulerException; + import org.apache.commons.lang.Validate; + import org.bukkit.plugin.IllegalPluginAccessException; + import org.bukkit.plugin.Plugin; +@@ -419,6 +422,8 @@ public class CraftScheduler implements BukkitScheduler { + msg, + throwable); + } ++ org.bukkit.Bukkit.getServer().getPluginManager().callEvent( ++ new ServerExceptionEvent(new ServerSchedulerException(msg, throwable, task))); + // Paper end + } finally { + currentTask = null; +@@ -426,7 +431,7 @@ public class CraftScheduler implements BukkitScheduler { + parsePending(); + } else { + debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); +- executor.execute(task); ++ executor.execute(new ServerSchedulerReportingWrapper(task)); // Paper + // We don't need to parse pending + // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) + } diff --git a/patches/server-unmapped/0001/0057-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch b/patches/server-unmapped/0001/0057-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch new file mode 100644 index 0000000000..e8a68b30bb --- /dev/null +++ b/patches/server-unmapped/0001/0057-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Tue, 8 Mar 2016 18:28:43 -0800 +Subject: [PATCH] Don't nest if we don't need to when cerealising text + components + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java +index edae451a54bfcd6b54e89c1619fb112a7763eb3b..f6a1c5ac9acb34b1ef2262721adbbb1a5b0feaf7 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java +@@ -40,7 +40,14 @@ public class PacketPlayOutChat implements Packet { + // Paper end + // Spigot start + if (components != null) { +- packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(components)); ++ //packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(components)); // Paper - comment, replaced with below ++ // Paper start - don't nest if we don't need to so that we can preserve formatting ++ if (this.components.length == 1) { ++ packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(this.components[0])); ++ } else { ++ packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(this.components)); ++ } ++ // Paper end + } else { + packetdataserializer.a(this.a); + } diff --git a/patches/server-unmapped/0001/0058-Disable-Scoreboards-for-non-players-by-default.patch b/patches/server-unmapped/0001/0058-Disable-Scoreboards-for-non-players-by-default.patch new file mode 100644 index 0000000000..47af46ff05 --- /dev/null +++ b/patches/server-unmapped/0001/0058-Disable-Scoreboards-for-non-players-by-default.patch @@ -0,0 +1,50 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index abbbe1786eb68af02f9d39650aad730ac44aac8a..3ac2ac3db9b1c271b3c21930bb13716669ff64d3 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -210,4 +210,9 @@ public class PaperWorldConfig { + private void disableTeleportationSuffocationCheck() { + disableTeleportationSuffocationCheck = getBoolean("disable-teleportation-suffocation-check", false); + } ++ ++ public boolean nonPlayerEntitiesOnScoreboards = false; ++ private void nonPlayerEntitiesOnScoreboards() { ++ nonPlayerEntitiesOnScoreboards = getBoolean("allow-non-player-entities-on-scoreboards", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 3c82d592903844b032e3fe4777dcb23be936cfc0..cfd7aec33628435ba7e12567c4c12d6db6130453 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2284,6 +2284,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + @Nullable + public ScoreboardTeamBase getScoreboardTeam() { ++ if (!this.world.paperConfig.nonPlayerEntitiesOnScoreboards && !(this instanceof EntityHuman)) { return null; } // Paper + return this.world.getScoreboard().getPlayerTeam(this.getName()); + } + +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 4e22a5e6d01506a681639500e90228e97d5772bb..5b0b0366a5a9782ec92a81e485832d47523c7dd1 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -741,6 +741,7 @@ public abstract class EntityLiving extends Entity { + if (nbttagcompound.hasKeyOfType("Team", 8)) { + String s = nbttagcompound.getString("Team"); + ScoreboardTeam scoreboardteam = this.world.getScoreboard().getTeam(s); ++ if (!world.paperConfig.nonPlayerEntitiesOnScoreboards && !(this instanceof EntityHuman)) { scoreboardteam = null; } // Paper + boolean flag = scoreboardteam != null && this.world.getScoreboard().addPlayerToTeam(this.getUniqueIDString(), scoreboardteam); + + if (!flag) { diff --git a/patches/server-unmapped/0001/0059-Add-methods-for-working-with-arrows-stuck-in-living-.patch b/patches/server-unmapped/0001/0059-Add-methods-for-working-with-arrows-stuck-in-living-.patch new file mode 100644 index 0000000000..6885002825 --- /dev/null +++ b/patches/server-unmapped/0001/0059-Add-methods-for-working-with-arrows-stuck-in-living-.patch @@ -0,0 +1,27 @@ +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 + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index d863fc9fa6b932b76a89871a09378a9c0697c108..c654026587bc9bf77b39f59a0c89991ac581da1e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -689,4 +689,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + getHandle().persistentInvisibility = invisible; + getHandle().setFlag(5, invisible); + } ++ ++ // Paper start ++ @Override ++ public int getArrowsStuck() { ++ return getHandle().getArrowCount(); ++ } ++ ++ @Override ++ public void setArrowsStuck(int arrows) { ++ getHandle().setArrowCount(arrows); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0060-Complete-resource-pack-API.patch b/patches/server-unmapped/0001/0060-Complete-resource-pack-API.patch new file mode 100644 index 0000000000..9d33dc5a78 --- /dev/null +++ b/patches/server-unmapped/0001/0060-Complete-resource-pack-API.patch @@ -0,0 +1,71 @@ +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/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 6a8567c355202560ee523c6dc68cac1ac3e562fd..69dc4431a430461ce242de5c1a1c3023367c99b7 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1604,7 +1604,11 @@ public class PlayerConnection implements PacketListenerPlayIn { + // CraftBukkit start + public void a(PacketPlayInResourcePackStatus packetplayinresourcepackstatus) { + PlayerConnectionUtils.ensureMainThread(packetplayinresourcepackstatus, this, this.player.getWorldServer()); +- this.server.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(getPlayer(), PlayerResourcePackStatusEvent.Status.values()[packetplayinresourcepackstatus.status.ordinal()])); ++ // Paper start ++ PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packetplayinresourcepackstatus.status.ordinal()]; ++ player.getBukkitEntity().setResourcePackStatus(packStatus); ++ this.server.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(getPlayer(), packStatus)); ++ // Paper end + } + // CraftBukkit end + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index da193bfc1d98e489eedc373ee87ab58b75d4377e..144faef77e69c6c3bf963327179a51bb4c37cc18 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -138,6 +138,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private double health = 20; + private boolean scaledHealth = false; + private double healthScale = 20; ++ // Paper start ++ private org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; ++ private String resourcePackHash; ++ // Paper end + + public CraftPlayer(CraftServer server, EntityPlayer entity) { + super(server, entity); +@@ -1871,6 +1875,32 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + public boolean getAffectsSpawning() { + return this.getHandle().affectsSpawning; + } ++ ++ @Override ++ public void setResourcePack(String url, String hash) { ++ Validate.notNull(url, "Resource pack URL cannot be null"); ++ Validate.notNull(hash, "Hash cannot be null"); ++ this.getHandle().setResourcePack(url, hash); ++ } ++ ++ @Override ++ public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status getResourcePackStatus() { ++ return this.resourcePackStatus; ++ } ++ ++ @Override ++ public String getResourcePackHash() { ++ return this.resourcePackHash; ++ } ++ ++ @Override ++ public boolean hasResourcePack() { ++ return this.resourcePackStatus == org.bukkit.event.player.PlayerResourcePackStatusEvent.Status.SUCCESSFULLY_LOADED; ++ } ++ ++ public void setResourcePackStatus(org.bukkit.event.player.PlayerResourcePackStatusEvent.Status status) { ++ this.resourcePackStatus = status; ++ } + // Paper end + + @Override diff --git a/patches/server-unmapped/0001/0061-Chunk-Save-Reattempt.patch b/patches/server-unmapped/0001/0061-Chunk-Save-Reattempt.patch new file mode 100644 index 0000000000..03f01b1583 --- /dev/null +++ b/patches/server-unmapped/0001/0061-Chunk-Save-Reattempt.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 4 Mar 2013 23:46:10 -0500 +Subject: [PATCH] Chunk Save Reattempt + +We commonly have "Stream Closed" errors on chunk saving, so this code should re-try to save the chunk in the event of failure and hopefully prevent rollbacks. + +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 1638f7902290e1bb233f11e5d0bbf83a9e863939..4bf3e0cb4602d33a2e00c502b1dd212032b22a8f 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 +@@ -265,7 +265,7 @@ public class RegionFile implements AutoCloseable { + return true; + } + } catch (IOException ioexception) { +- com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(ioexception); // Paper ++ com.destroystokyo.paper.util.SneakyThrow.sneaky(ioexception); // Paper - we want the upper try/catch to retry this + return false; + } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java +index de125077656f249d5cf9b76f07981b55e690e015..8310dd6bfc04b8ac0a51545baa3a264e6cb42eac 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java +@@ -11,6 +11,7 @@ import java.io.IOException; + import javax.annotation.Nullable; + import net.minecraft.nbt.NBTCompressedStreamTools; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.util.ExceptionSuppressor; + import net.minecraft.world.level.ChunkCoordIntPair; + +@@ -92,6 +93,7 @@ public final class RegionFileCache implements AutoCloseable { + + protected void write(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws IOException { + RegionFile regionfile = this.getFile(chunkcoordintpair, false); // CraftBukkit ++ int attempts = 0; Exception laste = null; while (attempts++ < 5) { try { // Paper + DataOutputStream dataoutputstream = regionfile.c(chunkcoordintpair); + Throwable throwable = null; + +@@ -115,6 +117,18 @@ public final class RegionFileCache implements AutoCloseable { + + } + ++ // Paper start ++ return; ++ } catch (Exception ex) { ++ laste = ex; ++ } ++ } ++ ++ if (laste != null) { ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(laste); ++ MinecraftServer.LOGGER.error("Failed to save chunk", laste); ++ } ++ // Paper end + } + + public void close() throws IOException { diff --git a/patches/server-unmapped/0001/0062-Default-loading-permissions.yml-before-plugins.patch b/patches/server-unmapped/0001/0062-Default-loading-permissions.yml-before-plugins.patch new file mode 100644 index 0000000000..58bca98dfe --- /dev/null +++ b/patches/server-unmapped/0001/0062-Default-loading-permissions.yml-before-plugins.patch @@ -0,0 +1,52 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 429b74474ced04d8dd8f038b8590b8dfe178bf4d..716f285e67019b8a62922d09c15883c99f9421aa 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -222,4 +222,9 @@ public class PaperConfig { + private static void useDisplayNameInQuit() { + useDisplayNameInQuit = getBoolean("use-display-name-in-quit-message", useDisplayNameInQuit); + } ++ ++ public static boolean loadPermsBeforePlugins = true; ++ private static void loadPermsBeforePlugins() { ++ loadPermsBeforePlugins = getBoolean("settings.load-permissions-yml-before-plugins", true); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f6a1d52239216984da4a9c87312c6ed085213935..b16d5f35d2c1c2ac29be2d6457ad83d0fcb98379 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -399,6 +399,7 @@ public final class CraftServer implements Server { + if (type == PluginLoadOrder.STARTUP) { + helpMap.clear(); + helpMap.initializeGeneralTopics(); ++ if (com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions(); // Paper + } + + Plugin[] plugins = pluginManager.getPlugins(); +@@ -418,7 +419,7 @@ public final class CraftServer implements Server { + commandMap.registerServerAliases(); + DefaultPermissions.registerCorePermissions(); + CraftDefaultPermissions.registerCorePermissions(); +- loadCustomPermissions(); ++ if (!com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions(); // Paper + helpMap.initializeCommands(); + syncCommands(); + } diff --git a/patches/server-unmapped/0001/0063-Allow-Reloading-of-Custom-Permissions.patch b/patches/server-unmapped/0001/0063-Allow-Reloading-of-Custom-Permissions.patch new file mode 100644 index 0000000000..68f46b46d2 --- /dev/null +++ b/patches/server-unmapped/0001/0063-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 b16d5f35d2c1c2ac29be2d6457ad83d0fcb98379..991ef9048822e52d1f39467f9b815eb1ba779f4b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2244,5 +2244,23 @@ public final class CraftServer implements Server { + } + return this.adventure$audiences; + } ++ ++ @Override ++ public void reloadPermissions() { ++ pluginManager.clearPermissions(); ++ if (com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) 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 (!com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions(); ++ DefaultPermissions.registerCorePermissions(); ++ CraftDefaultPermissions.registerCorePermissions(); ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0064-Remove-Metadata-on-reload.patch b/patches/server-unmapped/0001/0064-Remove-Metadata-on-reload.patch new file mode 100644 index 0000000000..9e40e136c4 --- /dev/null +++ b/patches/server-unmapped/0001/0064-Remove-Metadata-on-reload.patch @@ -0,0 +1,31 @@ +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 991ef9048822e52d1f39467f9b815eb1ba779f4b..96abe76ab9d71561740499b4dfcfa94ded1c1b53 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -873,8 +873,18 @@ public final class CraftServer implements Server { + world.paperConfig.init(); // Paper + } + ++ Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper + pluginManager.clearPlugins(); + commandMap.clearCommands(); ++ ++ // Paper start ++ for (Plugin plugin : pluginClone) { ++ entityMetadata.removeAll(plugin); ++ worldMetadata.removeAll(plugin); ++ playerMetadata.removeAll(plugin); ++ } ++ // Paper end ++ + resetRecipes(); + reloadData(); + org.spigotmc.SpigotConfig.registerCommands(); // Spigot diff --git a/patches/server-unmapped/0001/0065-Handle-Item-Meta-Inconsistencies.patch b/patches/server-unmapped/0001/0065-Handle-Item-Meta-Inconsistencies.patch new file mode 100644 index 0000000000..e87ee15fe2 --- /dev/null +++ b/patches/server-unmapped/0001/0065-Handle-Item-Meta-Inconsistencies.patch @@ -0,0 +1,331 @@ +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 4010152dccc93019f2e7f284d80b92bae0d91c34..d7c5065457d910f3e5481fda046d368d5f66f67b 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -9,6 +9,8 @@ import com.mojang.serialization.Codec; + import com.mojang.serialization.codecs.RecordCodecBuilder; + import java.text.DecimalFormat; + import java.text.DecimalFormatSymbols; ++import java.util.Collections; ++import java.util.Comparator; + import java.util.Locale; + import java.util.Objects; + import java.util.Optional; +@@ -120,6 +122,23 @@ public final class ItemStack { + private ShapeDetectorBlock n; + private boolean o; + ++ // Paper start ++ private static final java.util.Comparator enchantSorter = java.util.Comparator.comparing(o -> o.getString("id")); ++ private void processEnchantOrder(NBTTagCompound tag) { ++ if (tag == null || !tag.hasKeyOfType("Enchantments", 9)) { ++ return; ++ } ++ NBTTagList list = tag.getList("Enchantments", 10); ++ if (list.size() < 2) { ++ return; ++ } ++ try { ++ //noinspection unchecked ++ list.sort((Comparator) enchantSorter); // Paper ++ } catch (Exception ignored) {} ++ } ++ // Paper end ++ + public ItemStack(IMaterial imaterial) { + this(imaterial, 1); + } +@@ -162,6 +181,7 @@ public final class ItemStack { + if (nbttagcompound.hasKeyOfType("tag", 10)) { + // CraftBukkit start - make defensive copy as this data may be coming from the save thread + this.tag = (NBTTagCompound) nbttagcompound.getCompound("tag").clone(); ++ processEnchantOrder(this.tag); // Paper + this.getItem().b(this.tag); + // CraftBukkit end + } +@@ -680,6 +700,7 @@ public final class ItemStack { + // Paper end + public void setTag(@Nullable NBTTagCompound nbttagcompound) { + this.tag = nbttagcompound; ++ processEnchantOrder(this.tag); // Paper + if (this.getItem().usesDurability()) { + this.setDamage(this.getDamage()); + } +@@ -770,6 +791,7 @@ public final class ItemStack { + nbttagcompound.setString("id", String.valueOf(IRegistry.ENCHANTMENT.getKey(enchantment))); + nbttagcompound.setShort("lvl", (short) ((byte) i)); + nbttaglist.add(nbttagcompound); ++ processEnchantOrder(nbttagcompound); // Paper + } + + public boolean hasEnchantments() { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 275b943a59ef28c831a068987e111e84ebba3bb7..7221ac52c9f66ae0af6f6cbf15c8d47f9c0291a0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -178,28 +178,11 @@ public final class CraftItemStack extends ItemStack { + public void addUnsafeEnchantment(Enchantment ench, int level) { + Validate.notNull(ench, "Cannot add null enchantment"); + +- if (!makeTag(handle)) { +- return; +- } +- NBTTagList list = getEnchantmentList(handle); +- if (list == null) { +- list = new NBTTagList(); +- handle.getTag().set(ENCHANTMENTS.NBT, list); +- } +- int size = list.size(); +- +- for (int i = 0; i < size; i++) { +- NBTTagCompound tag = (NBTTagCompound) list.get(i); +- String id = tag.getString(ENCHANTMENTS_ID.NBT); +- if (id.equals(ench.getKey().toString())) { +- tag.setShort(ENCHANTMENTS_LVL.NBT, (short) level); +- return; +- } +- } +- NBTTagCompound tag = new NBTTagCompound(); +- tag.setString(ENCHANTMENTS_ID.NBT, ench.getKey().toString()); +- tag.setShort(ENCHANTMENTS_LVL.NBT, (short) level); +- list.add(tag); ++ // Paper start - Replace whole method ++ final ItemMeta itemMeta = getItemMeta(); ++ itemMeta.addEnchant(ench, level, true); ++ setItemMeta(itemMeta); ++ // Paper end + } + + static boolean makeTag(net.minecraft.world.item.ItemStack item) { +@@ -216,66 +199,33 @@ public final class CraftItemStack extends ItemStack { + + @Override + public boolean containsEnchantment(Enchantment ench) { +- return getEnchantmentLevel(ench) > 0; ++ return hasItemMeta() && getItemMeta().hasEnchant(ench); // Paper - use meta + } + + @Override + public int getEnchantmentLevel(Enchantment ench) { +- Validate.notNull(ench, "Cannot find null enchantment"); +- if (handle == null) { +- return 0; +- } +- return EnchantmentManager.getEnchantmentLevel(CraftEnchantment.getRaw(ench), handle); ++ return hasItemMeta() ? getItemMeta().getEnchantLevel(ench) : 0; // Paper - replace entire method with meta + } + + @Override + public int removeEnchantment(Enchantment ench) { + Validate.notNull(ench, "Cannot remove null enchantment"); + +- NBTTagList list = getEnchantmentList(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++) { +- NBTTagCompound enchantment = (NBTTagCompound) list.get(i); +- String id = enchantment.getString(ENCHANTMENTS_ID.NBT); +- if (id.equals(ench.getKey().toString())) { +- index = i; +- level = 0xffff & enchantment.getShort(ENCHANTMENTS_LVL.NBT); +- break; +- } +- } +- +- if (index == Integer.MIN_VALUE) { +- return 0; +- } +- if (size == 1) { +- handle.getTag().remove(ENCHANTMENTS.NBT); +- if (handle.getTag().isEmpty()) { +- handle.setTag(null); +- } +- return level; +- } +- +- // This is workaround for not having an index removal +- listCopy = new NBTTagList(); +- for (int i = 0; i < size; i++) { +- if (i != index) { +- listCopy.add(list.get(i)); +- } ++ // Paper start - replace entire method ++ final ItemMeta itemMeta = getItemMeta(); ++ int level = itemMeta.getEnchantLevel(ench); ++ if (level > 0) { ++ itemMeta.removeEnchant(ench); ++ setItemMeta(itemMeta); + } +- handle.getTag().set(ENCHANTMENTS.NBT, listCopy); ++ // Paper end + + return level; + } + + @Override + public Map getEnchantments() { +- return getEnchantments(handle); ++ return hasItemMeta() ? 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 7a11b2ddfa4244459253c918315aaab78ef2eb4a..57a6e66866ea82caccbbbfd55948a081f50f6bbe 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; +@@ -22,6 +23,7 @@ import java.lang.reflect.InvocationTargetException; + import java.util.ArrayList; + import java.util.Arrays; + import java.util.Collection; ++import java.util.Comparator; // Paper + import java.util.EnumSet; + import java.util.HashMap; + import java.util.Iterator; +@@ -32,6 +34,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; +@@ -271,7 +274,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + private List lore; // null and empty are two different states internally + private Integer customModelData; + private NBTTagCompound blockData; +- private Map enchantments; ++ private EnchantmentMap enchantments; // Paper + private Multimap attributeModifiers; + private int repairCost; + private int hideFlag; +@@ -282,7 +285,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + + private NBTTagCompound internalTag; +- private final Map unhandledTags = new HashMap(); ++ private final Map unhandledTags = new TreeMap<>(); // Paper + private CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY); + + private int version = CraftMagicNumbers.INSTANCE.getDataVersion(); // Internal use only +@@ -303,7 +306,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.blockData = meta.blockData; + + if (meta.enchantments != null) { // Spigot +- this.enchantments = new LinkedHashMap(meta.enchantments); ++ this.enchantments = new EnchantmentMap(meta.enchantments); // Paper + } + + if (meta.hasAttributeModifiers()) { +@@ -386,13 +389,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + +- static Map buildEnchantments(NBTTagCompound tag, ItemMetaKey key) { ++ static EnchantmentMap buildEnchantments(NBTTagCompound tag, ItemMetaKey key) { // Paper + if (!tag.hasKey(key.NBT)) { + return null; + } + + NBTTagList 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 = ((NBTTagCompound) ench.get(i)).getString(ENCHANTMENTS_ID.NBT); +@@ -545,13 +548,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(); +@@ -827,14 +830,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public Map getEnchants() { +- return hasEnchants() ? ImmutableMap.copyOf(enchantments) : ImmutableMap.of(); ++ return hasEnchants() ? ImmutableSortedMap.copyOfSorted(enchantments) : ImmutableMap.of(); // Paper + } + + @Override + public boolean addEnchant(Enchantment ench, int level, boolean ignoreRestrictions) { + Validate.notNull(ench, "Enchantment cannot be null"); + if (enchantments == null) { +- enchantments = new LinkedHashMap(4); ++ enchantments = new EnchantmentMap(); // Paper + } + + if (ignoreRestrictions || level >= ench.getStartLevel() && level <= ench.getMaxLevel()) { +@@ -1215,7 +1218,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); +@@ -1447,4 +1450,22 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return 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-unmapped/0001/0066-Configurable-Non-Player-Arrow-Despawn-Rate.patch b/patches/server-unmapped/0001/0066-Configurable-Non-Player-Arrow-Despawn-Rate.patch new file mode 100644 index 0000000000..0cc21d37c3 --- /dev/null +++ b/patches/server-unmapped/0001/0066-Configurable-Non-Player-Arrow-Despawn-Rate.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 18 Mar 2016 15:12:22 -0400 +Subject: [PATCH] Configurable Non Player Arrow Despawn Rate + +Can set a much shorter despawn rate for arrows that players can not pick up. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3ac2ac3db9b1c271b3c21930bb13716669ff64d3..3c78d3234054ce2dc46ef77decb6adb0cbd10620 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -215,4 +215,19 @@ public class PaperWorldConfig { + private void nonPlayerEntitiesOnScoreboards() { + nonPlayerEntitiesOnScoreboards = getBoolean("allow-non-player-entities-on-scoreboards", false); + } ++ ++ public int nonPlayerArrowDespawnRate = -1; ++ public int creativeArrowDespawnRate = -1; ++ private void nonPlayerArrowDespawnRate() { ++ nonPlayerArrowDespawnRate = getInt("non-player-arrow-despawn-rate", -1); ++ if (nonPlayerArrowDespawnRate == -1) { ++ nonPlayerArrowDespawnRate = spigotConfig.arrowDespawnRate; ++ } ++ creativeArrowDespawnRate = getInt("creative-arrow-despawn-rate", -1); ++ if (creativeArrowDespawnRate == -1) { ++ creativeArrowDespawnRate = spigotConfig.arrowDespawnRate; ++ } ++ log("Non Player Arrow Despawn Rate: " + nonPlayerArrowDespawnRate); ++ log("Creative Arrow Despawn Rate: " + creativeArrowDespawnRate); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java b/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java +index a05ee898bd360f49ea2807d06f93e27ada11ef63..9bd4a283a99f86c9a26f73e0bad0c3414d66ad55 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java +@@ -283,7 +283,7 @@ public abstract class EntityArrow extends IProjectile { + + protected void h() { + ++this.despawnCounter; +- if (this.despawnCounter >= ((this instanceof EntityThrownTrident) ? world.spigotConfig.tridentDespawnRate : world.spigotConfig.arrowDespawnRate)) { // Spigot ++ if (this.despawnCounter >= (fromPlayer == PickupStatus.CREATIVE_ONLY ? world.paperConfig.creativeArrowDespawnRate : (fromPlayer == PickupStatus.DISALLOWED ? world.paperConfig.nonPlayerArrowDespawnRate : ((this instanceof EntityThrownTrident) ? world.spigotConfig.tridentDespawnRate : world.spigotConfig.arrowDespawnRate)))) { // Spigot // Paper - TODO: Extract this to init? + this.die(); + } + diff --git a/patches/server-unmapped/0001/0067-Add-World-Util-Methods.patch b/patches/server-unmapped/0001/0067-Add-World-Util-Methods.patch new file mode 100644 index 0000000000..228342c690 --- /dev/null +++ b/patches/server-unmapped/0001/0067-Add-World-Util-Methods.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 18 Mar 2016 20:16:03 -0400 +Subject: [PATCH] Add World Util Methods + +Methods that can be used for other patches to help improve logic. + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 474e91b1a0a3fc2c5abb238f058e50ad787c22d9..045183ab75bac68b1da5e0899a15fa34cd9e956f 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -197,7 +197,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + public final Convertable.ConversionSession convertable; + public final UUID uuid; + +- public Chunk getChunkIfLoaded(int x, int z) { ++ @Override public Chunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI + return this.chunkProvider.getChunkAt(x, z, false); + } + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index b9951b9463c6c171825b212854b26ce639f36f21..5a0c93a6e9fe0854f69bce9a51a1e88d6827c494 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -297,11 +297,27 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + } + + @Override +- public Fluid getFluidIfLoaded(BlockPosition blockposition) { ++ public final Fluid getFluidIfLoaded(BlockPosition blockposition) { + IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); + + return chunk == null ? null : chunk.getFluid(blockposition); + } ++ ++ public final boolean isLoadedAndInBounds(BlockPosition blockposition) { // Paper - final for inline ++ return getWorldBorder().isInBounds(blockposition) && getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) != null; ++ } ++ ++ public Chunk getChunkIfLoaded(int x, int z) { // Overridden in WorldServer for ABI compat which has final ++ return ((WorldServer) this).getChunkProvider().getChunkAtIfLoadedImmediately(x, z); ++ } ++ public final Chunk getChunkIfLoaded(BlockPosition blockposition) { ++ return ((WorldServer) this).getChunkProvider().getChunkAtIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ } ++ ++ // reduces need to do isLoaded before getType ++ public final IBlockData getTypeIfLoadedAndInBounds(BlockPosition blockposition) { ++ return getWorldBorder().isInBounds(blockposition) ? getTypeIfLoaded(blockposition) : null; ++ } + // Paper end + + @Override +diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +index 2bb03f1cb9671a7754a68059219f783d4508eeb9..f16c76df5d7b184d57f4cc397f069eac9cc430cb 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -31,6 +31,7 @@ public class WorldBorder { + + public WorldBorder() {} + ++ public final boolean isInBounds(BlockPosition blockposition) { return this.a(blockposition); } // Paper - OBFHELPER + public boolean a(BlockPosition blockposition) { + return (double) (blockposition.getX() + 1) > this.e() && (double) blockposition.getX() < this.g() && (double) (blockposition.getZ() + 1) > this.f() && (double) blockposition.getZ() < this.h(); + } diff --git a/patches/server-unmapped/0001/0068-Custom-replacement-for-eaten-items.patch b/patches/server-unmapped/0001/0068-Custom-replacement-for-eaten-items.patch new file mode 100644 index 0000000000..137fae97b5 --- /dev/null +++ b/patches/server-unmapped/0001/0068-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/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 5b0b0366a5a9782ec92a81e485832d47523c7dd1..082a3efb8c03e6a0a35411107f3cf3776dee14bf 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -3205,9 +3205,10 @@ public abstract class EntityLiving extends Entity { + this.b(this.activeItem, 16); + // CraftBukkit start - fire PlayerItemConsumeEvent + ItemStack itemstack; ++ PlayerItemConsumeEvent event = null; // Paper + if (this instanceof EntityPlayer) { + org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitCopy(this.activeItem); +- PlayerItemConsumeEvent event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem); ++ event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem); // Paper + world.getServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { +@@ -3221,6 +3222,13 @@ public abstract class EntityLiving extends Entity { + } else { + itemstack = this.activeItem.a(this.world, 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.activeItem) { +@@ -3228,6 +3236,11 @@ public abstract class EntityLiving extends Entity { + } + + this.clearActiveItem(); ++ // Paper start - if the replacement is anything but the default, update the client inventory ++ if (this instanceof EntityPlayer && !com.google.common.base.Objects.equal(defaultReplacement, itemstack)) { ++ ((EntityPlayer) this).getBukkitEntity().updateInventory(); ++ } ++ // Paper end + } + + } diff --git a/patches/server-unmapped/0001/0069-handle-NaN-health-absorb-values-and-repair-bad-data.patch b/patches/server-unmapped/0001/0069-handle-NaN-health-absorb-values-and-repair-bad-data.patch new file mode 100644 index 0000000000..f61e3835d1 --- /dev/null +++ b/patches/server-unmapped/0001/0069-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/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 082a3efb8c03e6a0a35411107f3cf3776dee14bf..c7b40800343edb2c2a68786afb828c9dc3e3627f 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -702,7 +702,13 @@ public abstract class EntityLiving extends Entity { + + @Override + public void loadData(NBTTagCompound nbttagcompound) { +- this.setAbsorptionHearts(nbttagcompound.getFloat("AbsorptionAmount")); ++ // Paper start - jvm keeps optimizing the setter ++ float absorptionAmount = nbttagcompound.getFloat("AbsorptionAmount"); ++ if (Float.isNaN(absorptionAmount)) { ++ absorptionAmount = 0; ++ } ++ this.setAbsorptionHearts(absorptionAmount); ++ // Paper end + if (nbttagcompound.hasKeyOfType("Attributes", 9) && this.world != null && !this.world.isClientSide) { + this.getAttributeMap().a(nbttagcompound.getList("Attributes", 10)); + } +@@ -1151,6 +1157,10 @@ public abstract class EntityLiving extends Entity { + } + + public void setHealth(float f) { ++ // Paper start ++ if (Float.isNaN(f)) { f = getMaxHealth(); if (this.valid) { ++ System.err.println("[NAN-HEALTH] " + getName() + " had NaN health set"); ++ } } // Paper end + // CraftBukkit start - Handle scaled health + if (this instanceof EntityPlayer) { + org.bukkit.craftbukkit.entity.CraftPlayer player = ((EntityPlayer) this).getBukkitEntity(); +@@ -3045,7 +3055,7 @@ public abstract class EntityLiving extends Entity { + } + + public void setAbsorptionHearts(float f) { +- if (f < 0.0F) { ++ if (f < 0.0F || Float.isNaN(f)) { // Paper + f = 0.0F; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 144faef77e69c6c3bf963327179a51bb4c37cc18..b4a2758ee246252d6076e861b45d11dae6e33096 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1682,6 +1682,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void setRealHealth(double health) { ++ if (Double.isNaN(health)) {return;} // Paper + this.health = health; + } + diff --git a/patches/server-unmapped/0001/0070-Use-a-Shared-Random-for-Entities.patch b/patches/server-unmapped/0001/0070-Use-a-Shared-Random-for-Entities.patch new file mode 100644 index 0000000000..30d01d9c0c --- /dev/null +++ b/patches/server-unmapped/0001/0070-Use-a-Shared-Random-for-Entities.patch @@ -0,0 +1,42 @@ +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 cfd7aec33628435ba7e12567c4c12d6db6130453..e67c7f05620a4c76b4f806c11065f6e1938f0903 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -143,6 +143,21 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return tag.hasKey("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; + } + ++ // Paper start ++ public static Random SHARED_RANDOM = new Random() { ++ private boolean locked = false; ++ @Override ++ public synchronized void setSeed(long seed) { ++ if (locked) { ++ LogManager.getLogger().error("Ignoring setSeed on Entity.SHARED_RANDOM", new Throwable()); ++ } else { ++ super.setSeed(seed); ++ locked = true; ++ } ++ } ++ }; ++ // Paper end ++ + private CraftEntity bukkitEntity; + + public CraftEntity getBukkitEntity() { +@@ -272,7 +287,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + this.x = Vec3D.ORIGIN; + this.am = 1.0F; + this.an = 1.0F; +- this.random = new Random(); ++ this.random = SHARED_RANDOM; // Paper + this.fireTicks = -this.getMaxFireTicks(); + this.M = new Object2DoubleArrayMap(2); + this.justCreated = true; diff --git a/patches/server-unmapped/0001/0071-Configurable-spawn-chances-for-skeleton-horses.patch b/patches/server-unmapped/0001/0071-Configurable-spawn-chances-for-skeleton-horses.patch new file mode 100644 index 0000000000..a0c1e7b121 --- /dev/null +++ b/patches/server-unmapped/0001/0071-Configurable-spawn-chances-for-skeleton-horses.patch @@ -0,0 +1,36 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3c78d3234054ce2dc46ef77decb6adb0cbd10620..cd64fb9d0c6d123e1c86cb33f12cd9cefc9f80d0 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -230,4 +230,12 @@ public class PaperWorldConfig { + log("Non Player Arrow Despawn Rate: " + nonPlayerArrowDespawnRate); + log("Creative Arrow Despawn Rate: " + creativeArrowDespawnRate); + } ++ ++ public double skeleHorseSpawnChance; ++ private void skeleHorseSpawnChance() { ++ skeleHorseSpawnChance = getDouble("skeleton-horse-thunder-spawn-chance", 0.01D); ++ if (skeleHorseSpawnChance < 0) { ++ skeleHorseSpawnChance = 0.01D; // Vanilla value ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 045183ab75bac68b1da5e0899a15fa34cd9e956f..a5ee8bf7904444ff6fd82260a66a81c9af479f9e 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -590,7 +590,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + blockposition = this.a(this.a(j, 0, k, 15)); + if (this.isRainingAt(blockposition)) { + DifficultyDamageScaler difficultydamagescaler = this.getDamageScaler(blockposition); +- boolean flag1 = this.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.b() * 0.01D; ++ boolean flag1 = this.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.b() * paperConfig.skeleHorseSpawnChance; // Paper + + if (flag1) { + EntityHorseSkeleton entityhorseskeleton = (EntityHorseSkeleton) EntityTypes.SKELETON_HORSE.a((World) this); diff --git a/patches/server-unmapped/0001/0072-Optimize-isValidLocation-getType-and-getBlockData-fo.patch b/patches/server-unmapped/0001/0072-Optimize-isValidLocation-getType-and-getBlockData-fo.patch new file mode 100644 index 0000000000..74473d3e72 --- /dev/null +++ b/patches/server-unmapped/0001/0072-Optimize-isValidLocation-getType-and-getBlockData-fo.patch @@ -0,0 +1,206 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 02:07:55 -0600 +Subject: [PATCH] Optimize isValidLocation, getType and getBlockData for + inlining + +Hot methods, so reduce # of instructions for the method. + +Move is valid location test to the BlockPosition class so that it can access local variables. + +Replace all calls to the new place to the unnecessary forward. + +Optimize getType and getBlockData to manually inline and optimize the calls + +diff --git a/src/main/java/net/minecraft/core/BaseBlockPosition.java b/src/main/java/net/minecraft/core/BaseBlockPosition.java +index 25fdd55a7548cfaa45a541ad77f22f33c33e7471..4b56683336fdab06804efdc8ca1f7c130b77291f 100644 +--- a/src/main/java/net/minecraft/core/BaseBlockPosition.java ++++ b/src/main/java/net/minecraft/core/BaseBlockPosition.java +@@ -22,6 +22,15 @@ public class BaseBlockPosition implements Comparable { + private int b;public final void setY(final int y) { this.b = y; } // Paper - OBFHELPER + private int e;public final void setZ(final int z) { this.e = z; } // Paper - OBFHELPER + ++ // Paper start ++ public boolean isValidLocation() { ++ return getX() >= -30000000 && getZ() >= -30000000 && getX() < 30000000 && getZ() < 30000000 && getY() >= 0 && getY() < 256; ++ } ++ public boolean isInvalidYLocation() { ++ return b < 0 || b >= 256; ++ } ++ // Paper end ++ + public BaseBlockPosition(int i, int j, int k) { + this.a = i; + this.b = j; +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 5a0c93a6e9fe0854f69bce9a51a1e88d6827c494..bc80420ad7d9e64ebff3052e5ca6a760f515ac37 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -239,7 +239,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + } + + public static boolean isValidLocation(BlockPosition blockposition) { +- return !isOutsideWorld(blockposition) && D(blockposition); ++ return blockposition.isValidLocation(); // Paper - use better/optimized check + } + + public static boolean l(BlockPosition blockposition) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index 4b3de29b1a6e9d75b28962073c62bbe8d666165f..fdc491f978560c394eec22116572585f9bbdec9f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -348,12 +348,27 @@ public class Chunk implements IChunkAccess { + return this.sections; + } + +- @Override ++ // Paper start - Optimize getBlockData to reduce instructions ++ public final IBlockData getBlockData(BlockPosition pos) { return getBlockData(pos.getX(), pos.getY(), pos.getZ()); } // Paper + public IBlockData getType(BlockPosition blockposition) { +- int i = blockposition.getX(); +- int j = blockposition.getY(); +- int k = blockposition.getZ(); ++ return this.getBlockData(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ } ++ ++ public IBlockData getType(final int x, final int y, final int z) { ++ return getBlockData(x, y, z); ++ } ++ public final IBlockData getBlockData(final int x, final int y, final int z) { ++ // Method body / logic copied from below ++ final int i = y >> 4; ++ if (y < 0 || i >= this.sections.length || this.sections[i] == null || this.sections[i].nonEmptyBlockCount == 0) { ++ return Blocks.AIR.getBlockData(); ++ } ++ // Inlined ChunkSection.getType() and DataPaletteBlock.a(int,int,int) ++ return this.sections[i].blockIds.a((y & 15) << 8 | (z & 15) << 4 | x & 15); ++ } + ++ public IBlockData getBlockData_unused(int i, int j, int k) { ++ // Paper end + if (this.world.isDebugWorld()) { + IBlockData iblockdata = null; + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkEmpty.java b/src/main/java/net/minecraft/world/level/chunk/ChunkEmpty.java +index 395d21afaabcbd99f9ce0551d647f5db9507a518..89efd0b68b04457e1cd617dcc8bb1a6ea1c4717c 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkEmpty.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkEmpty.java +@@ -23,7 +23,7 @@ import net.minecraft.world.phys.AxisAlignedBB; + + public class ChunkEmpty extends Chunk { + +- private static final BiomeBase[] b = (BiomeBase[]) SystemUtils.a((Object) (new BiomeBase[BiomeStorage.a]), (abiomebase) -> { ++ private static final BiomeBase[] b = SystemUtils.a((new BiomeBase[BiomeStorage.a]), (abiomebase) -> { // Paper - decompile error + Arrays.fill(abiomebase, BiomeRegistry.a); + }); + +@@ -31,6 +31,11 @@ public class ChunkEmpty extends Chunk { + super(world, chunkcoordintpair, new BiomeStorage(world.r().b(IRegistry.ay), ChunkEmpty.b)); + } + ++ // Paper start ++ @Override public IBlockData getType(int x, int y, int z) { ++ return Blocks.VOID_AIR.getBlockData(); ++ } ++ // Paper end + @Override + public IBlockData getType(BlockPosition blockposition) { + return Blocks.VOID_AIR.getBlockData(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +index a4e2eb1a753e8fcb48982d78fe80e505bce5c476..eea4a30428293eaf7afbe303a37adec60b44c2b4 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +@@ -13,10 +13,10 @@ public class ChunkSection { + + public static final DataPalette GLOBAL_PALETTE = new DataPaletteGlobal<>(Block.REGISTRY_ID, Blocks.AIR.getBlockData()); + private final int yPos; +- private short nonEmptyBlockCount; ++ short nonEmptyBlockCount; // Paper - package-private + private short tickingBlockCount; + private short e; +- private final DataPaletteBlock blockIds; ++ final DataPaletteBlock blockIds; // Paper - package-private + + public ChunkSection(int i) { + this(i, (short) 0, (short) 0, (short) 0); +@@ -30,8 +30,8 @@ public class ChunkSection { + this.blockIds = new DataPaletteBlock<>(ChunkSection.GLOBAL_PALETTE, Block.REGISTRY_ID, GameProfileSerializer::c, GameProfileSerializer::a, Blocks.AIR.getBlockData()); + } + +- public IBlockData getType(int i, int j, int k) { +- return (IBlockData) this.blockIds.a(i, j, k); ++ public final IBlockData getType(int i, int j, int k) { // Paper ++ return this.blockIds.a(j << 8 | k << 4 | i); // Paper - inline + } + + public Fluid b(int i, int j, int k) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +index e397b871b846c3a90bc75d0e1cf0683b6a3d0ca9..8928157b01bb4f0dfe043732777b33708c23cda7 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java ++++ b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +@@ -133,7 +133,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { + } + + public T a(int i, int j, int k) { +- return this.a(b(i, j, k)); ++ return this.a(j << 8 | k << 4 | i); // Paper - inline + } + + protected T a(int i) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/IChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/IChunkAccess.java +index 2cd04abd72f1135446182ad6294003e526f99a4b..e570dc58efa56bd0aa5ada5575b4054ee38d505e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/IChunkAccess.java ++++ b/src/main/java/net/minecraft/world/level/chunk/IChunkAccess.java +@@ -25,6 +25,7 @@ import org.apache.logging.log4j.LogManager; + + public interface IChunkAccess extends IBlockAccess, IStructureAccess { + ++ IBlockData getType(final int x, final int y, final int z); // Paper + @Nullable + IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag); + +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 7572ca53a5cca8ca5085d18c24048b85dda4daa9..9eeb99a21a6ed7f71ff64cf4cfdff646d31abbcf 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -113,16 +113,18 @@ public class ProtoChunk implements IChunkAccess { + + @Override + public IBlockData getType(BlockPosition blockposition) { +- int i = blockposition.getY(); +- +- if (World.b(i)) { ++ return getType(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ } ++ // Paper start ++ public IBlockData getType(final int x, final int y, final int z) { ++ if (y < 0 || y >= 256) { + return Blocks.VOID_AIR.getBlockData(); + } else { +- ChunkSection chunksection = this.getSections()[i >> 4]; +- +- return ChunkSection.a(chunksection) ? Blocks.AIR.getBlockData() : chunksection.getType(blockposition.getX() & 15, i & 15, blockposition.getZ() & 15); ++ ChunkSection chunksection = this.getSections()[y >> 4]; ++ return chunksection == Chunk.EMPTY_CHUNK_SECTION || chunksection.c() ? Blocks.AIR.getBlockData() : chunksection.getType(x & 15, y & 15, z & 15); + } + } ++ // Paper end + + @Override + public Fluid getFluid(BlockPosition blockposition) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunkExtension.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunkExtension.java +index c059d3d055c35b492680556e8605966e2caaf7fd..9351e6ba541d440c485b6e4a3209170c5756e31e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunkExtension.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunkExtension.java +@@ -42,6 +42,11 @@ public class ProtoChunkExtension extends ProtoChunk { + public IBlockData getType(BlockPosition blockposition) { + return this.a.getType(blockposition); + } ++ // Paper start ++ public final IBlockData getType(final int x, final int y, final int z) { ++ return this.a.getBlockData(x, y, z); ++ } ++ // Paper end + + @Override + public Fluid getFluid(BlockPosition blockposition) { diff --git a/patches/server-unmapped/0001/0073-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch b/patches/server-unmapped/0001/0073-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch new file mode 100644 index 0000000000..578d491e78 --- /dev/null +++ b/patches/server-unmapped/0001/0073-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch @@ -0,0 +1,95 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 28 Mar 2016 19:55:45 -0400 +Subject: [PATCH] Only process BlockPhysicsEvent if a plugin has a listener + +Saves on some object allocation and processing when no plugin listens to this + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 06071e15851d5d27f1c9a0d60a764a6214e0ba0f..33139f9dc6a9c6030f565b01c9b6fd411cafa710 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1292,6 +1292,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0; // Paper + + this.methodProfiler.a(() -> { + return worldserver + " " + worldserver.getDimensionKey().a(); +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index a5ee8bf7904444ff6fd82260a66a81c9af479f9e..c5baf9c448761f24c4fd49d7c4bade7dee43edf4 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -196,6 +196,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + private int tickPosition; + public final Convertable.ConversionSession convertable; + public final UUID uuid; ++ public boolean hasPhysicsEvent = true; // Paper + + @Override public Chunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI + return this.chunkProvider.getChunkAt(x, z, false); +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index bc80420ad7d9e64ebff3052e5ca6a760f515ac37..3e6f65b51ce8506fbb0ce8d0d8727c2682e58641 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -458,7 +458,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + // CraftBukkit start + iblockdata1.b(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam + CraftWorld world = ((WorldServer) this).getWorld(); +- if (world != null) { ++ if (world != null && ((WorldServer)this).hasPhysicsEvent) { // Paper + BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); + this.getServer().getPluginManager().callEvent(event); + +@@ -560,7 +560,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + try { + // CraftBukkit start + CraftWorld world = ((WorldServer) this).getWorld(); +- if (world != null) { ++ if (world != null && ((WorldServer)this).hasPhysicsEvent) { // Paper + BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata), world.getBlockAt(blockposition1.getX(), blockposition1.getY(), blockposition1.getZ())); + this.getServer().getPluginManager().callEvent(event); + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockPlant.java b/src/main/java/net/minecraft/world/level/block/BlockPlant.java +index 33a5c5a4dc1478ab211dbb2e09df87570b06644f..97dfe5c5e3ea1d9691de87ffbf4b1a29a83a65b4 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockPlant.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockPlant.java +@@ -2,6 +2,7 @@ package net.minecraft.world.level.block; + + import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; ++import net.minecraft.server.level.WorldServer; + import net.minecraft.world.level.GeneratorAccess; + import net.minecraft.world.level.IBlockAccess; + import net.minecraft.world.level.IWorldReader; +@@ -23,7 +24,7 @@ public class BlockPlant extends Block { + public IBlockData updateState(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { + // CraftBukkit start + if (!iblockdata.canPlace(generatoraccess, blockposition)) { +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(generatoraccess, blockposition).isCancelled()) { ++ if (!(generatoraccess instanceof WorldServer && ((WorldServer) generatoraccess).hasPhysicsEvent) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(generatoraccess, blockposition).isCancelled()) { // Paper + return Blocks.AIR.getBlockData(); + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/BlockTallPlant.java b/src/main/java/net/minecraft/world/level/block/BlockTallPlant.java +index ca22187625f7ac6c43b663fd4d66cbf0c943c655..1a5d29ecc9edc52bac14ed5d05ef5376fd5b8a9c 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockTallPlant.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockTallPlant.java +@@ -3,6 +3,7 @@ package net.minecraft.world.level.block; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; ++import net.minecraft.server.level.WorldServer; + import net.minecraft.world.entity.EntityLiving; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.item.ItemStack; +@@ -83,7 +84,7 @@ public class BlockTallPlant extends BlockPlant { + + protected static void b(World world, BlockPosition blockposition, IBlockData iblockdata, EntityHuman entityhuman) { + // CraftBukkit start +- if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world, blockposition).isCancelled()) { ++ if (((WorldServer)world).hasPhysicsEvent && org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world, blockposition).isCancelled()) { // Paper + return; + } + // CraftBukkit end diff --git a/patches/server-unmapped/0001/0074-Entity-AddTo-RemoveFrom-World-Events.patch b/patches/server-unmapped/0001/0074-Entity-AddTo-RemoveFrom-World-Events.patch new file mode 100644 index 0000000000..e910aa110a --- /dev/null +++ b/patches/server-unmapped/0001/0074-Entity-AddTo-RemoveFrom-World-Events.patch @@ -0,0 +1,27 @@ +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/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index c5baf9c448761f24c4fd49d7c4bade7dee43edf4..b40089319329a0843c4d74ebd6189fc4089e319a 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1214,7 +1214,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + if (entity instanceof EntityInsentient) { + this.navigators.remove(((EntityInsentient) entity).getNavigation()); + } +- ++ new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid + entity.valid = false; // CraftBukkit + } + +@@ -1252,6 +1252,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + entity.origin = entity.getBukkitEntity().getLocation(); + } + // Paper end ++ new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid + } + + } diff --git a/patches/server-unmapped/0001/0075-Configurable-Chunk-Inhabited-Time.patch b/patches/server-unmapped/0001/0075-Configurable-Chunk-Inhabited-Time.patch new file mode 100644 index 0000000000..aec9c86c64 --- /dev/null +++ b/patches/server-unmapped/0001/0075-Configurable-Chunk-Inhabited-Time.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 28 Mar 2016 20:46:14 -0400 +Subject: [PATCH] Configurable Chunk Inhabited Time + +Vanilla stores how long a chunk has been active on a server, and dynamically scales some +aspects of vanilla gameplay to this factor. + +For people who want all chunks to be treated equally, you can chose a fixed value. + +This allows to fine-tune vanilla gameplay. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index cd64fb9d0c6d123e1c86cb33f12cd9cefc9f80d0..74ba5dbb83c13ce1721619b755036a7864a1fb90 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -238,4 +238,14 @@ public class PaperWorldConfig { + skeleHorseSpawnChance = 0.01D; // Vanilla value + } + } ++ ++ public int fixedInhabitedTime; ++ private void fixedInhabitedTime() { ++ if (PaperConfig.version < 16) { ++ if (!config.getBoolean("world-settings.default.use-chunk-inhabited-timer", true)) config.set("world-settings.default.fixed-chunk-inhabited-time", 0); ++ if (!config.getBoolean("world-settings." + worldName + ".use-chunk-inhabited-timer", true)) config.set("world-settings." + worldName + ".fixed-chunk-inhabited-time", 0); ++ set("use-chunk-inhabited-timer", null); ++ } ++ fixedInhabitedTime = getInt("fixed-chunk-inhabited-time", -1); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index fdc491f978560c394eec22116572585f9bbdec9f..b6898cd6e6117fef65198db32b98a64c806811d4 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -1022,7 +1022,7 @@ public class Chunk implements IChunkAccess { + + @Override + public long getInhabitedTime() { +- return this.inhabitedTime; ++ return world.paperConfig.fixedInhabitedTime < 0 ? this.inhabitedTime : world.paperConfig.fixedInhabitedTime; // Paper + } + + @Override diff --git a/patches/server-unmapped/0001/0076-EntityPathfindEvent.patch b/patches/server-unmapped/0001/0076-EntityPathfindEvent.patch new file mode 100644 index 0000000000..84caa9a7de --- /dev/null +++ b/patches/server-unmapped/0001/0076-EntityPathfindEvent.patch @@ -0,0 +1,107 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 28 Mar 2016 21:22:26 -0400 +Subject: [PATCH] EntityPathfindEvent + +Fires when an Entity decides to start moving to a location. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/Navigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/Navigation.java +index 942e03578836524ba746bc37699677eb06cc7803..703d06b2b29f1500301d82df78dc377141085145 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/Navigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/Navigation.java +@@ -75,7 +75,7 @@ public class Navigation extends NavigationAbstract { + + @Override + public PathEntity a(Entity entity, int i) { +- return this.a(entity.getChunkCoordinates(), i); ++ return this.a(entity.getChunkCoordinates(), entity, i); // Paper - Forward target entity + } + + private int u() { +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +index 8848a7552a0ef3944560a71f71620c6bd0f08c10..58225877ce4f2533c19d34e143ae374dc289bce5 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +@@ -10,6 +10,7 @@ import net.minecraft.core.BaseBlockPosition; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.IPosition; + import net.minecraft.network.protocol.game.PacketDebug; ++import net.minecraft.server.MCUtil; + import net.minecraft.util.MathHelper; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityInsentient; +@@ -28,7 +29,7 @@ import net.minecraft.world.phys.Vec3D; + + public abstract class NavigationAbstract { + +- protected final EntityInsentient a; ++ protected final EntityInsentient a; public Entity getEntity() { return a; } // Paper - OBFHELPER + protected final World b; + @Nullable + protected PathEntity c; +@@ -115,16 +116,26 @@ public abstract class NavigationAbstract { + + @Nullable + public PathEntity a(BlockPosition blockposition, int i) { +- return this.a(ImmutableSet.of(blockposition), 8, false, i); ++ // Paper start - add target parameter ++ return this.a(blockposition, null, i); ++ } ++ @Nullable public PathEntity a(BlockPosition blockposition, Entity target, int i) { ++ return this.a(ImmutableSet.of(blockposition), target, 8, false, i); ++ // Paper end + } + + @Nullable + public PathEntity a(Entity entity, int i) { +- return this.a(ImmutableSet.of(entity.getChunkCoordinates()), 16, true, i); ++ return this.a(ImmutableSet.of(entity.getChunkCoordinates()), entity, 16, true, i); // Paper + } + + @Nullable ++ // Paper start - Add target + protected PathEntity a(Set set, int i, boolean flag, int j) { ++ return this.a(set, null, i, flag, j); ++ } ++ @Nullable protected PathEntity a(Set set, Entity target, int i, boolean flag, int j) { ++ // Paper end + if (set.isEmpty()) { + return null; + } else if (this.a.locY() < 0.0D) { +@@ -134,6 +145,23 @@ public abstract class NavigationAbstract { + } else if (this.c != null && !this.c.c() && set.contains(this.p)) { + return this.c; + } else { ++ // Paper start - Pathfind event ++ boolean copiedSet = false; ++ for (BlockPosition possibleTarget : set) { ++ if (!new com.destroystokyo.paper.event.entity.EntityPathfindEvent(getEntity().getBukkitEntity(), ++ MCUtil.toLocation(getEntity().world, possibleTarget), target == null ? null : target.getBukkitEntity()).callEvent()) { ++ if (!copiedSet) { ++ copiedSet = true; ++ set = new java.util.HashSet<>(set); ++ } ++ // note: since we copy the set this remove call is safe, since we're iterating over the old copy ++ set.remove(possibleTarget); ++ if (set.isEmpty()) { ++ return null; ++ } ++ } ++ } ++ // Paper end + this.b.getMethodProfiler().enter("pathfind"); + float f = (float) this.a.b(GenericAttributes.FOLLOW_RANGE); + BlockPosition blockposition = flag ? this.a.getChunkCoordinates().up() : this.a.getChunkCoordinates(); +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationFlying.java b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationFlying.java +index 2e1efe7a048f64d494260d10a4ae5dba86af5e6c..f5664b8c0762f775f3cd106d156eb74b48bcedc2 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationFlying.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationFlying.java +@@ -37,7 +37,7 @@ public class NavigationFlying extends NavigationAbstract { + + @Override + public PathEntity a(Entity entity, int i) { +- return this.a(entity.getChunkCoordinates(), i); ++ return this.a(entity.getChunkCoordinates(), entity, i); // Paper - Forward target entity + } + + @Override diff --git a/patches/server-unmapped/0001/0077-Sanitise-RegionFileCache-and-make-configurable.patch b/patches/server-unmapped/0001/0077-Sanitise-RegionFileCache-and-make-configurable.patch new file mode 100644 index 0000000000..d60c778cea --- /dev/null +++ b/patches/server-unmapped/0001/0077-Sanitise-RegionFileCache-and-make-configurable.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Antony Riley +Date: Tue, 29 Mar 2016 08:22:55 +0300 +Subject: [PATCH] Sanitise RegionFileCache and make configurable. + +RegionFileCache prior to this patch would close every single open region +file upon reaching a size of 256. +This patch modifies that behaviour so it closes the the least recently +used RegionFile. +The implementation uses a LinkedHashMap as an LRU cache (modified from HashMap). +The maximum size of the RegionFileCache is also made configurable. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 716f285e67019b8a62922d09c15883c99f9421aa..439dcc6effdc91830d2b7ede9063982998b37120 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -227,4 +227,9 @@ public class PaperConfig { + private static void loadPermsBeforePlugins() { + loadPermsBeforePlugins = getBoolean("settings.load-permissions-yml-before-plugins", true); + } ++ ++ public static int regionFileCacheSize = 256; ++ private static void regionFileCacheSize() { ++ regionFileCacheSize = Math.max(getInt("settings.region-file-cache-size", 256), 4); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java +index 8310dd6bfc04b8ac0a51545baa3a264e6cb42eac..75b10a3755392870d8f5b51239a09a0e7fd75a42 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java +@@ -33,7 +33,7 @@ public final class RegionFileCache implements AutoCloseable { + if (regionfile != null) { + return regionfile; + } else { +- if (this.cache.size() >= 256) { ++ if (this.cache.size() >= com.destroystokyo.paper.PaperConfig.regionFileCacheSize) { // Paper - configurable + ((RegionFile) this.cache.removeLast()).close(); + } + diff --git a/patches/server-unmapped/0001/0078-Do-not-load-chunks-for-Pathfinding.patch b/patches/server-unmapped/0001/0078-Do-not-load-chunks-for-Pathfinding.patch new file mode 100644 index 0000000000..6086fc3ad7 --- /dev/null +++ b/patches/server-unmapped/0001/0078-Do-not-load-chunks-for-Pathfinding.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 31 Mar 2016 19:17:58 -0400 +Subject: [PATCH] Do not load chunks for Pathfinding + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +index 58225877ce4f2533c19d34e143ae374dc289bce5..d71a6e5991629ce59c8529d7cc8064960e385236 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +@@ -48,7 +48,7 @@ public abstract class NavigationAbstract { + private BlockPosition p; + private int q; + private float r; +- private final Pathfinder s; ++ private final Pathfinder s; public Pathfinder getPathfinder() { return this.s; } // Paper - OBFHELPER + private boolean t; + + public NavigationAbstract(EntityInsentient entityinsentient, World world) { +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java b/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java +index 39cd22a820fdc4c75aefb625b45b0c8c6ce1f199..5784be69098805e4d550a0923ac8daa5aada73f9 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java +@@ -20,7 +20,7 @@ public class Pathfinder { + + private final PathPoint[] a = new PathPoint[32]; + private final int b; +- private final PathfinderAbstract c; ++ private final PathfinderAbstract c; public PathfinderAbstract getPathfinder() { return this.c; } // Paper - OBFHELPER + private final Path d = new Path(); + + public Pathfinder(PathfinderAbstract pathfinderabstract, int i) { +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java +index ed9c1dfbc84b9573784e6531186b3cd9513ddf75..d14f2800237c2a80912bf6f2d418a9ba9031070d 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java +@@ -479,7 +479,12 @@ public class PathfinderNormal extends PathfinderAbstract { + for (int j1 = -1; j1 <= 1; ++j1) { + if (l != 0 || j1 != 0) { + blockposition_mutableblockposition.d(i + l, j + i1, k + j1); +- IBlockData iblockdata = iblockaccess.getType(blockposition_mutableblockposition); ++ // Paper start ++ IBlockData iblockdata = iblockaccess.getTypeIfLoaded(blockposition_mutableblockposition); ++ if (iblockdata == null) { ++ pathtype = PathType.BLOCKED; ++ } else { ++ // Paper end + + if (iblockdata.a(Blocks.CACTUS)) { + return PathType.DANGER_CACTUS; +@@ -496,6 +501,7 @@ public class PathfinderNormal extends PathfinderAbstract { + if (iblockaccess.getFluid(blockposition_mutableblockposition).a((Tag) TagsFluid.WATER)) { + return PathType.WATER_BORDER; + } ++ } // Paper + } + } + } +@@ -505,7 +511,8 @@ public class PathfinderNormal extends PathfinderAbstract { + } + + protected static PathType b(IBlockAccess iblockaccess, BlockPosition blockposition) { +- IBlockData iblockdata = iblockaccess.getType(blockposition); ++ IBlockData iblockdata = iblockaccess.getTypeIfLoaded(blockposition); // Paper ++ if (iblockdata == null) return PathType.BLOCKED; // Paper + Block block = iblockdata.getBlock(); + Material material = iblockdata.getMaterial(); + diff --git a/patches/server-unmapped/0001/0079-Add-PlayerUseUnknownEntityEvent.patch b/patches/server-unmapped/0001/0079-Add-PlayerUseUnknownEntityEvent.patch new file mode 100644 index 0000000000..5401f07df1 --- /dev/null +++ b/patches/server-unmapped/0001/0079-Add-PlayerUseUnknownEntityEvent.patch @@ -0,0 +1,40 @@ +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 + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInUseEntity.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInUseEntity.java +index 9f3f8568ef9484ba226deaa6429f819c325b7a26..ce63f3e5ac4d1a4311c0ebeb7574d999d45987d9 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInUseEntity.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInUseEntity.java +@@ -11,7 +11,7 @@ import net.minecraft.world.phys.Vec3D; + + public class PacketPlayInUseEntity implements Packet { + +- private int a; ++ private int a; public int getEntityId() { return this.a; } // Paper - add accessor + private PacketPlayInUseEntity.EnumEntityUseAction action; + private Vec3D c; + private EnumHand d; +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 69dc4431a430461ce242de5c1a1c3023367c99b7..7cb946ff73de5171debe34b37a784b6ed4e09150 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -2199,6 +2199,16 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + } + } ++ // Paper start - fire event ++ else { ++ this.server.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerUseUnknownEntityEvent( ++ this.getPlayer(), ++ packetplayinuseentity.getEntityId(), ++ packetplayinuseentity.b() == PacketPlayInUseEntity.EnumEntityUseAction.ATTACK, ++ packetplayinuseentity.c() == EnumHand.MAIN_HAND ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND ++ )); ++ } ++ // Paper end + + } + diff --git a/patches/server-unmapped/0001/0080-Fix-reducedDebugInfo-not-initialized-on-client.patch b/patches/server-unmapped/0001/0080-Fix-reducedDebugInfo-not-initialized-on-client.patch new file mode 100644 index 0000000000..063b7e7b45 --- /dev/null +++ b/patches/server-unmapped/0001/0080-Fix-reducedDebugInfo-not-initialized-on-client.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Sat, 2 Apr 2016 20:37:03 -0400 +Subject: [PATCH] Fix reducedDebugInfo not initialized on client + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index cfd0af520dd3dcf364a3ffd03a74e3b9ee6045af..152aa38788a21638aab7cfe2dc187671f1143bde 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -246,6 +246,7 @@ public abstract class PlayerList { + playerconnection.sendPacket(new PacketPlayOutHeldItemSlot(entityplayer.inventory.itemInHandIndex)); + playerconnection.sendPacket(new PacketPlayOutRecipeUpdate(this.server.getCraftingManager().b())); + playerconnection.sendPacket(new PacketPlayOutTags(this.server.getTagRegistry())); ++ playerconnection.sendPacket(new PacketPlayOutEntityStatus(entityplayer, (byte) (worldserver1.getGameRules().getBoolean(GameRules.REDUCED_DEBUG_INFO) ? 22 : 23))); // Paper - fix this rule not being initialized on the client + this.d(entityplayer); + entityplayer.getStatisticManager().c(); + entityplayer.getRecipeBook().a(entityplayer); diff --git a/patches/server-unmapped/0001/0081-Configurable-Grass-Spread-Tick-Rate.patch b/patches/server-unmapped/0001/0081-Configurable-Grass-Spread-Tick-Rate.patch new file mode 100644 index 0000000000..78ce2d0001 --- /dev/null +++ b/patches/server-unmapped/0001/0081-Configurable-Grass-Spread-Tick-Rate.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 3 Apr 2016 16:28:17 -0400 +Subject: [PATCH] Configurable Grass Spread Tick Rate + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 74ba5dbb83c13ce1721619b755036a7864a1fb90..db2dddd12f54e6d15916c4cee623676541de37fb 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -248,4 +248,10 @@ public class PaperWorldConfig { + } + fixedInhabitedTime = getInt("fixed-chunk-inhabited-time", -1); + } ++ ++ public int grassUpdateRate = 1; ++ private void grassUpdateRate() { ++ grassUpdateRate = Math.max(0, getInt("grass-spread-tick-rate", grassUpdateRate)); ++ log("Grass Spread Tick Rate: " + grassUpdateRate); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/BlockDirtSnowSpreadable.java b/src/main/java/net/minecraft/world/level/block/BlockDirtSnowSpreadable.java +index a98392f06e66959ec1b75df8d2ecf3b5267980af..712596420af83e6e1b9d147ae2fd8d8a1f36e1b9 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockDirtSnowSpreadable.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockDirtSnowSpreadable.java +@@ -3,6 +3,7 @@ package net.minecraft.world.level.block; + import java.util.Random; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.WorldServer; + import net.minecraft.tags.Tag; + import net.minecraft.tags.TagsFluid; +@@ -41,6 +42,7 @@ public abstract class BlockDirtSnowSpreadable extends BlockDirtSnow { + + @Override + public void tick(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, Random random) { ++ if (this instanceof BlockGrass && worldserver.paperConfig.grassUpdateRate != 1 && (worldserver.paperConfig.grassUpdateRate < 1 || (MinecraftServer.currentTick + blockposition.hashCode()) % worldserver.paperConfig.grassUpdateRate != 0)) { return; } // Paper + if (!b(iblockdata, (IWorldReader) worldserver, blockposition)) { + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(worldserver, blockposition, Blocks.DIRT.getBlockData()).isCancelled()) { diff --git a/patches/server-unmapped/0001/0082-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch b/patches/server-unmapped/0001/0082-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch new file mode 100644 index 0000000000..567be06e15 --- /dev/null +++ b/patches/server-unmapped/0001/0082-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch @@ -0,0 +1,18 @@ +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/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 3e6f65b51ce8506fbb0ce8d0d8727c2682e58641..aa083505d8b2f9a3d74d6387c27300d47f4366e8 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -518,6 +518,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public void b(BlockPosition blockposition, IBlockData iblockdata, IBlockData iblockdata1) {} + + public void applyPhysics(BlockPosition blockposition, Block block) { ++ if (captureBlockStates) { return; } // Paper - Cancel all physics during placement + this.a(blockposition.west(), block, blockposition); + this.a(blockposition.east(), block, blockposition); + this.a(blockposition.down(), block, blockposition); diff --git a/patches/server-unmapped/0001/0083-Optimize-DataBits.patch b/patches/server-unmapped/0001/0083-Optimize-DataBits.patch new file mode 100644 index 0000000000..61a9ac3f61 --- /dev/null +++ b/patches/server-unmapped/0001/0083-Optimize-DataBits.patch @@ -0,0 +1,84 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 5 Apr 2016 21:38:58 -0400 +Subject: [PATCH] Optimize DataBits + +Remove Debug checks as these are super hot and causing noticeable hits + +Before: http://i.imgur.com/nQsMzAE.png +After: http://i.imgur.com/nJ46crB.png + +Optimize redundant converting of static fields into an unsigned long each call by precomputing it in ctor + +diff --git a/src/main/java/net/minecraft/util/DataBits.java b/src/main/java/net/minecraft/util/DataBits.java +index 0c0576c8730069fb5364d8383dec8ab7e698658d..c4f3b680512fb15cea01ad12d0a00c6e60bf34b7 100644 +--- a/src/main/java/net/minecraft/util/DataBits.java ++++ b/src/main/java/net/minecraft/util/DataBits.java +@@ -13,8 +13,8 @@ public class DataBits { + private final long d; + private final int e; + private final int f; +- private final int g; +- private final int h; ++ private final int g;private final long g_unsigned; // Paper - referenced in b(int) with 2 Integer.toUnsignedLong calls ++ private final int h;private final long h_unsigned; // Paper + private final int i; + + public DataBits(int i, int j) { +@@ -29,8 +29,8 @@ public class DataBits { + this.f = (char) (64 / i); + int k = 3 * (this.f - 1); + +- this.g = DataBits.a[k + 0]; +- this.h = DataBits.a[k + 1]; ++ this.g = DataBits.a[k + 0]; this.g_unsigned = Integer.toUnsignedLong(this.g); // Paper ++ this.h = DataBits.a[k + 1]; this.h_unsigned = Integer.toUnsignedLong(this.h); // Paper + this.i = DataBits.a[k + 2]; + int l = (j + this.f - 1) / this.f; + +@@ -47,15 +47,15 @@ public class DataBits { + } + + private int b(int i) { +- long j = Integer.toUnsignedLong(this.g); +- long k = Integer.toUnsignedLong(this.h); ++ //long j = Integer.toUnsignedLong(this.g); // Paper ++ //long k = Integer.toUnsignedLong(this.h); // Paper + +- return (int) ((long) i * j + k >> 32 >> this.i); ++ return (int) ((long) i * this.g_unsigned + this.h_unsigned >> 32 >> this.i); // Paper + } + +- public int a(int i, int j) { +- Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); +- Validate.inclusiveBetween(0L, this.d, (long) j); ++ public final int a(int i, int j) { // Paper - make final for inline ++ //Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper ++ //Validate.inclusiveBetween(0L, this.d, (long) j); // Paper + int k = this.b(i); + long l = this.b[k]; + int i1 = (i - k * this.f) * this.c; +@@ -65,9 +65,9 @@ public class DataBits { + return j1; + } + +- public void b(int i, int j) { +- Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); +- Validate.inclusiveBetween(0L, this.d, (long) j); ++ public final void b(int i, int j) { // Paper - make final for inline ++ //Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper ++ //Validate.inclusiveBetween(0L, this.d, (long) j); // Paper + int k = this.b(i); + long l = this.b[k]; + int i1 = (i - k * this.f) * this.c; +@@ -75,8 +75,8 @@ public class DataBits { + this.b[k] = l & ~(this.d << i1) | ((long) j & this.d) << i1; + } + +- public int a(int i) { +- Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); ++ public final int a(int i) { // Paper - make final for inline ++ //Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper + int j = this.b(i); + long k = this.b[j]; + int l = (i - j * this.f) * this.c; diff --git a/patches/server-unmapped/0001/0084-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch b/patches/server-unmapped/0001/0084-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch new file mode 100644 index 0000000000..562a552216 --- /dev/null +++ b/patches/server-unmapped/0001/0084-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 6 Apr 2016 01:04:23 -0500 +Subject: [PATCH] Option to use vanilla per-world scoreboard coloring on names + +This change is basically a bandaid to fix CB's complete and utter lack +of support for vanilla scoreboard name modifications. + +In the future, finding a way to merge the vanilla expectations in with +bukkit's concept of a display name would be preferable. There was a PR +for this on CB at one point but I can't find it. We may need to do this +ourselves at some point in the future. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index db2dddd12f54e6d15916c4cee623676541de37fb..1942f5224aaebb18adb591d6f70a419cfc1a7bdd 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -254,4 +254,9 @@ public class PaperWorldConfig { + grassUpdateRate = Math.max(0, getInt("grass-spread-tick-rate", grassUpdateRate)); + log("Grass Spread Tick Rate: " + grassUpdateRate); + } ++ ++ public boolean useVanillaScoreboardColoring; ++ private void useVanillaScoreboardColoring() { ++ useVanillaScoreboardColoring = getBoolean("use-vanilla-world-scoreboard-name-coloring", false); ++ } + } +diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +index 0cb9368dcffe08a1ab004c6e2803b43eb655ac43..7e3b69763fbb6bac43cabc741020952fd24323e9 100644 +--- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java ++++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +@@ -17,7 +17,11 @@ import net.minecraft.network.chat.ChatMessageType; + import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.world.scores.ScoreboardTeam; ++import net.minecraft.world.scores.ScoreboardTeamBase; + import org.bukkit.Bukkit; ++import org.bukkit.ChatColor; ++import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.entity.CraftPlayer; + import org.bukkit.craftbukkit.util.LazyPlayerSet; + import org.bukkit.craftbukkit.util.Waitable; +@@ -179,10 +183,22 @@ public final class ChatProcessor { + } + + private static String legacyDisplayName(final CraftPlayer player) { ++ if (((CraftWorld) player.getWorld()).getHandle().paperConfig.useVanillaScoreboardColoring) { ++ final EntityPlayer ep = player.getHandle(); ++ IChatBaseComponent name = ep.getDisplayName(); ++ final ScoreboardTeamBase team = ep.getScoreboardTeam(); ++ if (team != null) { ++ name = team.getFormattedName(name); ++ } ++ return PaperAdventure.LEGACY_SECTION_UXRC.serialize(PaperAdventure.asAdventure(name)) + ChatColor.RESET; ++ } + return player.getDisplayName(); + } + + private static Component displayName(final CraftPlayer player) { ++ if (((CraftWorld) player.getWorld()).getHandle().paperConfig.useVanillaScoreboardColoring) { ++ return PaperAdventure.asAdventure(ScoreboardTeam.a(player.getHandle().getScoreboardTeam(), player.getHandle().getDisplayName())); ++ } + return player.displayName(); + } + diff --git a/patches/server-unmapped/0001/0085-Workaround-for-setting-passengers-on-players.patch b/patches/server-unmapped/0001/0085-Workaround-for-setting-passengers-on-players.patch new file mode 100644 index 0000000000..736a0b97ca --- /dev/null +++ b/patches/server-unmapped/0001/0085-Workaround-for-setting-passengers-on-players.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sun, 10 Apr 2016 03:23:32 -0500 +Subject: [PATCH] Workaround for setting passengers on players + +SPIGOT-1915 & GH-114 + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index b4a2758ee246252d6076e861b45d11dae6e33096..facf2a79144a2d0fbea907c084becaea7bbdc48c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -874,6 +874,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return true; + } + ++ // Paper start - Ugly workaround for SPIGOT-1915 & GH-114 ++ @Override ++ public boolean setPassenger(org.bukkit.entity.Entity passenger) { ++ boolean wasSet = super.setPassenger(passenger); ++ if (wasSet) { ++ this.getHandle().playerConnection.sendPacket(new net.minecraft.network.protocol.game.PacketPlayOutMount(this.getHandle())); ++ } ++ return wasSet; ++ } ++ // Paper end ++ + @Override + public void setSneaking(boolean sneak) { + getHandle().setSneaking(sneak); diff --git a/patches/server-unmapped/0001/0086-Remove-unused-World-Tile-Entity-List.patch b/patches/server-unmapped/0001/0086-Remove-unused-World-Tile-Entity-List.patch new file mode 100644 index 0000000000..20c3230d67 --- /dev/null +++ b/patches/server-unmapped/0001/0086-Remove-unused-World-Tile-Entity-List.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 13 Apr 2016 00:25:28 -0400 +Subject: [PATCH] Remove unused World Tile Entity List + +Massive hit to performance and it is completely unnecessary. + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index b40089319329a0843c4d74ebd6189fc4089e319a..2412c2fa22abe171254f7fe49d319bcd6cc533ff 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1722,7 +1722,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + bufferedwriter.write(String.format("entities: %d\n", this.entitiesById.size())); +- bufferedwriter.write(String.format("block_entities: %d\n", this.tileEntityList.size())); ++ bufferedwriter.write(String.format("block_entities: %d\n", this.tileEntityListTick.size())); // Paper - remove unused list + bufferedwriter.write(String.format("block_ticks: %d\n", this.getBlockTickList().a())); + bufferedwriter.write(String.format("fluid_ticks: %d\n", this.getFluidTickList().a())); + bufferedwriter.write("distance_manager: " + playerchunkmap.e().c() + "\n"); +@@ -1861,7 +1861,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + private void a(Writer writer) throws IOException { + CSVWriter csvwriter = CSVWriter.a().a("x").a("y").a("z").a("type").a(writer); +- Iterator iterator = this.tileEntityList.iterator(); ++ Iterator iterator = this.tileEntityListTick.iterator(); // Paper - remove unused list + + while (iterator.hasNext()) { + TileEntity tileentity = (TileEntity) iterator.next(); +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index aa083505d8b2f9a3d74d6387c27300d47f4366e8..453ae9a0976c309af5bf59cbb785dc56fd7ffe82 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -91,7 +91,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public static final ResourceKey THE_NETHER = ResourceKey.a(IRegistry.L, new MinecraftKey("the_nether")); + public static final ResourceKey THE_END = ResourceKey.a(IRegistry.L, new MinecraftKey("the_end")); + private static final EnumDirection[] a = EnumDirection.values(); +- public final List tileEntityList = Lists.newArrayList(); ++ //public final List tileEntityList = Lists.newArrayList(); // Paper - remove unused list + public final List tileEntityListTick = Lists.newArrayList(); + protected final List tileEntityListPending = Lists.newArrayList(); + protected final java.util.Set tileEntityListUnload = com.google.common.collect.Sets.newHashSet(); +@@ -683,9 +683,9 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + }, tileentity::getPosition}); + } + +- boolean flag = this.tileEntityList.add(tileentity); ++ boolean flag = true; // Paper - remove unused list + +- if (flag && tileentity instanceof ITickable) { ++ if (flag && tileentity instanceof ITickable && !this.tileEntityListTick.contains(tileentity)) { // Paper + this.tileEntityListTick.add(tileentity); + } + +@@ -721,7 +721,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + timings.tileEntityTick.startTiming(); // Spigot + if (!this.tileEntityListUnload.isEmpty()) { + this.tileEntityListTick.removeAll(this.tileEntityListUnload); +- this.tileEntityList.removeAll(this.tileEntityListUnload); ++ //this.tileEntityList.removeAll(this.tileEntityListUnload); // Paper - remove unused list + this.tileEntityListUnload.clear(); + } + +@@ -781,7 +781,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + tilesThisCycle--; + this.tileEntityListTick.remove(tileTickPosition--); + // Spigot end +- this.tileEntityList.remove(tileentity); ++ //this.tileEntityList.remove(tileentity); // Paper - remove unused list + if (this.isLoaded(tileentity.getPosition())) { + this.getChunkAtWorldCoords(tileentity.getPosition()).removeTileEntity(tileentity.getPosition()); + } +@@ -811,7 +811,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + this.notify(tileentity1.getPosition(), iblockdata, iblockdata, 3); + // CraftBukkit start + // From above, don't screw this up - SPIGOT-1746 +- if (!this.tileEntityList.contains(tileentity1)) { ++ if (true) { // Paper - remove unused list + this.a(tileentity1); + } + // CraftBukkit end +@@ -957,7 +957,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + } else { + if (tileentity != null) { + this.tileEntityListPending.remove(tileentity); +- this.tileEntityList.remove(tileentity); ++ //this.tileEntityList.remove(tileentity); // Paper - remove unused list + this.tileEntityListTick.remove(tileentity); + } + diff --git a/patches/server-unmapped/0001/0087-Don-t-tick-Skulls-unused-code.patch b/patches/server-unmapped/0001/0087-Don-t-tick-Skulls-unused-code.patch new file mode 100644 index 0000000000..26489896ab --- /dev/null +++ b/patches/server-unmapped/0001/0087-Don-t-tick-Skulls-unused-code.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 13 Apr 2016 00:30:10 -0400 +Subject: [PATCH] Don't tick Skulls - unused code + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySkull.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySkull.java +index 87a5f352c8a6336c65008d6e21a771fd6332773c..22217f24b4a87f10b6d5a3e37d23a1164af84ace 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySkull.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySkull.java +@@ -33,7 +33,7 @@ import net.minecraft.server.MinecraftServer; + import net.minecraft.world.entity.player.EntityHuman; + // Spigot end + +-public class TileEntitySkull extends TileEntity implements ITickable { ++public class TileEntitySkull extends TileEntity /*implements ITickable*/ { // Paper - remove tickable + + @Nullable + private static UserCache userCache; +@@ -136,7 +136,7 @@ public class TileEntitySkull extends TileEntity implements ITickable { + + } + +- @Override ++ // Paper - remove override + public void tick() { + IBlockData iblockdata = this.getBlock(); + diff --git a/patches/server-unmapped/0001/0088-Configurable-Player-Collision.patch b/patches/server-unmapped/0001/0088-Configurable-Player-Collision.patch new file mode 100644 index 0000000000..450c44ab69 --- /dev/null +++ b/patches/server-unmapped/0001/0088-Configurable-Player-Collision.patch @@ -0,0 +1,131 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 439dcc6effdc91830d2b7ede9063982998b37120..504efea7b6f50a0d17f4f353781953dfb18bdeca 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -232,4 +232,9 @@ public class PaperConfig { + private static void regionFileCacheSize() { + regionFileCacheSize = Math.max(getInt("settings.region-file-cache-size", 256), 4); + } ++ ++ public static boolean enablePlayerCollisions = true; ++ private static void enablePlayerCollisions() { ++ enablePlayerCollisions = getBoolean("settings.enable-player-collisions", true); ++ } + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutScoreboardTeam.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutScoreboardTeam.java +index bc40f2cbe1645fd60c4cee106b90f17cd043d32d..c1bb5c325286119891e8d68ce8f7328c99edb486 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutScoreboardTeam.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutScoreboardTeam.java +@@ -112,7 +112,7 @@ public class PacketPlayOutScoreboardTeam implements Packet toRemove = scoreboard.getTeams().stream().filter(team -> team.getName().startsWith("collideRule_")).map(ScoreboardTeam::getName).collect(java.util.stream.Collectors.toList()); ++ for (String teamName : toRemove) { ++ scoreboard.removeTeam(scoreboard.getTeam(teamName)); // Clean up after ourselves ++ } ++ ++ if (!com.destroystokyo.paper.PaperConfig.enablePlayerCollisions) { ++ this.getPlayerList().collideRuleTeamName = org.apache.commons.lang3.StringUtils.left("collideRule_" + java.util.concurrent.ThreadLocalRandom.current().nextInt(), 16); ++ ScoreboardTeam collideTeam = scoreboard.createTeam(this.getPlayerList().collideRuleTeamName); ++ collideTeam.setCanSeeFriendlyInvisibles(false); // Because we want to mimic them not being on a team at all ++ } ++ // Paper end ++ + this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD); + this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP)); + this.serverConnection.acceptConnections(); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 152aa38788a21638aab7cfe2dc187671f1143bde..f9e9e51b0b0dcbf2a8424c7c14bd2cbb0d899e82 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -86,6 +86,7 @@ import net.minecraft.world.level.storage.SavedFile; + import net.minecraft.world.level.storage.WorldData; + import net.minecraft.world.level.storage.WorldNBTStorage; + import net.minecraft.world.phys.Vec3D; ++import net.minecraft.world.scores.Scoreboard; + import net.minecraft.world.scores.ScoreboardObjective; + import net.minecraft.world.scores.ScoreboardTeam; + import net.minecraft.world.scores.ScoreboardTeamBase; +@@ -145,6 +146,7 @@ public abstract class PlayerList { + // CraftBukkit start + private CraftServer cserver; + private final Map playersByName = new java.util.HashMap<>(); ++ public @Nullable String collideRuleTeamName; // Paper - Team name used for collideRule + + public PlayerList(MinecraftServer minecraftserver, IRegistryCustom.Dimension iregistrycustom_dimension, WorldNBTStorage worldnbtstorage, int i) { + this.cserver = minecraftserver.server = new CraftServer((DedicatedServer) minecraftserver, this); +@@ -376,6 +378,13 @@ public abstract class PlayerList { + } + + entityplayer.syncInventory(); ++ // Paper start - Add to collideRule team if needed ++ final Scoreboard scoreboard = this.getServer().getWorldServer(World.OVERWORLD).getScoreboard(); ++ final ScoreboardTeam collideRuleTeam = scoreboard.getTeam(collideRuleTeamName); ++ if (this.collideRuleTeamName != null && collideRuleTeam != null && entityplayer.getScoreboardTeam() == null) { ++ scoreboard.addPlayerToTeam(entityplayer.getName(), collideRuleTeam); ++ } ++ // Paper end + // CraftBukkit - Moved from above, added world + PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", entityplayer.getDisplayName().getString(), s1, entityplayer.getId(), worldserver1.worldDataServer.getName(), entityplayer.locX(), entityplayer.locY(), entityplayer.locZ()); + } +@@ -496,6 +505,16 @@ public abstract class PlayerList { + entityplayer.playerTick(); // SPIGOT-924 + // CraftBukkit end + ++ // Paper start - Remove from collideRule team if needed ++ if (this.collideRuleTeamName != null) { ++ final Scoreboard scoreBoard = this.server.getWorldServer(World.OVERWORLD).getScoreboard(); ++ final ScoreboardTeam team = scoreBoard.getTeam(this.collideRuleTeamName); ++ if (entityplayer.getScoreboardTeam() == team && team != null) { ++ scoreBoard.removePlayerFromTeam(entityplayer.getName(), team); ++ } ++ } ++ // Paper end ++ + this.savePlayerFile(entityplayer); + if (entityplayer.isPassenger()) { + Entity entity = entityplayer.getRootVehicle(); +@@ -1144,6 +1163,13 @@ public abstract class PlayerList { + } + // CraftBukkit end + ++ // Paper start - Remove collideRule team if it exists ++ if (this.collideRuleTeamName != null) { ++ final Scoreboard scoreboard = this.getServer().getWorldServer(World.OVERWORLD).getScoreboard(); ++ final ScoreboardTeam team = scoreboard.getTeam(this.collideRuleTeamName); ++ if (team != null) scoreboard.removeTeam(team); ++ } ++ // Paper end + } + + // CraftBukkit start diff --git a/patches/server-unmapped/0001/0089-Add-handshake-event-to-allow-plugins-to-handle-clien.patch b/patches/server-unmapped/0001/0089-Add-handshake-event-to-allow-plugins-to-handle-clien.patch new file mode 100644 index 0000000000..ec7652c216 --- /dev/null +++ b/patches/server-unmapped/0001/0089-Add-handshake-event-to-allow-plugins-to-handle-clien.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Wed, 13 Apr 2016 20:21:38 -0700 +Subject: [PATCH] Add handshake event to allow plugins to handle client + handshaking logic themselves + + +diff --git a/src/main/java/net/minecraft/server/network/HandshakeListener.java b/src/main/java/net/minecraft/server/network/HandshakeListener.java +index b1714750538a0107d9922812d08471921b755925..31011ec34e5eeae29705f6ec167fb2e832284873 100644 +--- a/src/main/java/net/minecraft/server/network/HandshakeListener.java ++++ b/src/main/java/net/minecraft/server/network/HandshakeListener.java +@@ -25,7 +25,7 @@ public class HandshakeListener implements PacketHandshakingInListener { + // CraftBukkit end + private static final IChatBaseComponent a = new ChatComponentText("Ignoring status request"); + private final MinecraftServer b; +- private final NetworkManager c; ++ private final NetworkManager c; final NetworkManager getNetworkManager() { return this.c; } // Paper - OBFHELPER + + public HandshakeListener(MinecraftServer minecraftserver, NetworkManager networkmanager) { + this.b = minecraftserver; +@@ -84,8 +84,34 @@ public class HandshakeListener implements PacketHandshakingInListener { + this.c.close(chatmessage); + } else { + this.c.setPacketListener(new LoginListener(this.b, this.c)); ++ // Paper start - handshake event ++ boolean proxyLogicEnabled = org.spigotmc.SpigotConfig.bungee; ++ boolean handledByEvent = false; ++ // Try and handle the handshake through the event ++ if (com.destroystokyo.paper.event.player.PlayerHandshakeEvent.getHandlerList().getRegisteredListeners().length != 0) { // Hello? Can you hear me? ++ java.net.InetSocketAddress socketAddress = (java.net.InetSocketAddress) this.getNetworkManager().socketAddress; ++ com.destroystokyo.paper.event.player.PlayerHandshakeEvent event = new com.destroystokyo.paper.event.player.PlayerHandshakeEvent(packethandshakinginsetprotocol.hostname, socketAddress.getAddress().getHostAddress(), !proxyLogicEnabled); ++ if (event.callEvent()) { ++ // If we've failed somehow, let the client know so and go no further. ++ if (event.isFailed()) { ++ chatmessage = new ChatMessage(event.getFailMessage()); ++ this.getNetworkManager().sendPacket(new PacketLoginOutDisconnect(chatmessage)); ++ this.getNetworkManager().close(chatmessage); ++ return; ++ } ++ ++ if (event.getServerHostname() != null) packethandshakinginsetprotocol.hostname = event.getServerHostname(); ++ if (event.getSocketAddressHostname() != null) this.getNetworkManager().socketAddress = new java.net.InetSocketAddress(event.getSocketAddressHostname(), socketAddress.getPort()); ++ this.getNetworkManager().spoofedUUID = event.getUniqueId(); ++ this.getNetworkManager().spoofedProfile = gson.fromJson(event.getPropertiesJson(), com.mojang.authlib.properties.Property[].class); ++ handledByEvent = true; // Hooray, we did it! ++ } ++ } ++ // Don't try and handle default logic if it's been handled by the event. ++ if (!handledByEvent && proxyLogicEnabled) { ++ // Paper end + // Spigot Start +- if (org.spigotmc.SpigotConfig.bungee) { ++ //if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! + String[] split = packethandshakinginsetprotocol.hostname.split("\00"); + if ( split.length == 3 || split.length == 4 ) { + packethandshakinginsetprotocol.hostname = split[0]; diff --git a/patches/server-unmapped/0001/0090-Configurable-RCON-IP-address.patch b/patches/server-unmapped/0001/0090-Configurable-RCON-IP-address.patch new file mode 100644 index 0000000000..05f3461032 --- /dev/null +++ b/patches/server-unmapped/0001/0090-Configurable-RCON-IP-address.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 16 Apr 2016 00:39:33 -0400 +Subject: [PATCH] Configurable RCON IP address + +For servers with multiple IP's, ability to bind to a specific interface. + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +index 0ecff9f5e2ba444b196d80da341ff851dd5ce26f..b7cf02301c02ed0a6b696384e656426762ae2105 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -67,6 +67,8 @@ public class DedicatedServerProperties extends PropertyManager.EditableProperty whiteList; + public final GeneratorSettings generatorSettings; + ++ public final String rconIp; // Paper - Add rcon ip ++ + // CraftBukkit start + public DedicatedServerProperties(Properties properties, IRegistryCustom iregistrycustom, OptionSet optionset) { + super(properties, optionset); +@@ -118,6 +120,10 @@ public class DedicatedServerProperties extends PropertyManager> { + }; + } + +- @Nullable +- private String c(String s) { ++ @Nullable String getSettingIfExists(final String path) { return this.c(path); } // Paper - OBFHELPER ++ @Nullable private String c(String s) { // Paper - OBFHELPER + return (String) getOverride(s, this.properties.getProperty(s)); // CraftBukkit + } + +diff --git a/src/main/java/net/minecraft/server/rcon/thread/RemoteControlListener.java b/src/main/java/net/minecraft/server/rcon/thread/RemoteControlListener.java +index 797a450a08da1b799e32fae2a71a7a50bb90d127..3b3e21d1d86629d6c5e06108e53d1c5e807074d8 100644 +--- a/src/main/java/net/minecraft/server/rcon/thread/RemoteControlListener.java ++++ b/src/main/java/net/minecraft/server/rcon/thread/RemoteControlListener.java +@@ -62,7 +62,7 @@ public class RemoteControlListener extends RemoteConnectionThread { + @Nullable + public static RemoteControlListener a(IMinecraftServer iminecraftserver) { + DedicatedServerProperties dedicatedserverproperties = iminecraftserver.getDedicatedServerProperties(); +- String s = iminecraftserver.h_(); ++ String s = dedicatedserverproperties.rconIp; // Paper - Configurable rcon ip + + if (s.isEmpty()) { + s = "0.0.0.0"; diff --git a/patches/server-unmapped/0001/0091-Prevent-Fire-from-loading-chunks-wrongly-spread.patch b/patches/server-unmapped/0001/0091-Prevent-Fire-from-loading-chunks-wrongly-spread.patch new file mode 100644 index 0000000000..c379b25eba --- /dev/null +++ b/patches/server-unmapped/0001/0091-Prevent-Fire-from-loading-chunks-wrongly-spread.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 17 Apr 2016 17:27:09 -0400 +Subject: [PATCH] Prevent Fire from loading chunks & wrongly spread + +This causes the nether to spam unload/reload chunks, plus overall +bad behavior. + +This also stops fire from spreading to illegal locations. + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockFire.java b/src/main/java/net/minecraft/world/level/block/BlockFire.java +index ee5400fd3e493e1f0518a9e47ddbc997e7a0fb92..c22fad0038fdb0769e23db782e3341206fbd80f9 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockFire.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockFire.java +@@ -135,7 +135,7 @@ public class BlockFire extends BlockFireAbstract { + BlockStateBoolean blockstateboolean = (BlockStateBoolean) BlockFire.h.get(enumdirection); + + if (blockstateboolean != null) { +- iblockdata1 = (IBlockData) iblockdata1.set(blockstateboolean, this.e(iblockaccess.getType(blockposition.shift(enumdirection)))); ++ iblockdata1 = (IBlockData) iblockdata1.set(blockstateboolean, this.e(iblockaccess.getTypeIfLoaded(blockposition.shift(enumdirection)))); // Paper - prevent chunk loads + } + } + +@@ -215,6 +215,7 @@ public class BlockFire extends BlockFireAbstract { + } + + blockposition_mutableblockposition.a((BaseBlockPosition) blockposition, l, j1, i1); ++ if (blockposition_mutableblockposition.isInvalidYLocation() || !worldserver.isLoaded(blockposition_mutableblockposition)) continue; // Paper + int l1 = this.a((IWorldReader) worldserver, (BlockPosition) blockposition_mutableblockposition); + + if (l1 > 0) { +@@ -260,10 +261,16 @@ public class BlockFire extends BlockFireAbstract { + } + + private void trySpread(World world, BlockPosition blockposition, int i, Random random, int j, BlockPosition sourceposition) { // CraftBukkit add sourceposition +- int k = this.getBurnChance(world.getType(blockposition)); ++ // Paper start ++ final IBlockData iblockdata = world.getTypeIfLoaded(blockposition); ++ if (iblockdata == null) { ++ return; ++ } ++ int k = this.getBurnChance(iblockdata); ++ // Paper end + + if (random.nextInt(i) < k) { +- IBlockData iblockdata = world.getType(blockposition); ++ //IBlockData iblockdata = world.getType(blockposition); // Paper + + // CraftBukkit start + org.bukkit.block.Block theBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); +@@ -309,7 +316,7 @@ public class BlockFire extends BlockFireAbstract { + for (int j = 0; j < i; ++j) { + EnumDirection enumdirection = aenumdirection[j]; + +- if (this.e(iblockaccess.getType(blockposition.shift(enumdirection)))) { ++ if (this.e(iblockaccess.getTypeIfLoaded(blockposition.shift(enumdirection)))) { // Paper - prevent chunk loads + return true; + } + } +@@ -327,7 +334,12 @@ public class BlockFire extends BlockFireAbstract { + + for (int k = 0; k < j; ++k) { + EnumDirection enumdirection = aenumdirection[k]; +- IBlockData iblockdata = iworldreader.getType(blockposition.shift(enumdirection)); ++ // Paper start ++ IBlockData iblockdata = iworldreader.getTypeIfLoaded(blockposition.shift(enumdirection)); ++ if (iblockdata == null) { ++ continue; ++ } ++ // Paper end + + i = Math.max(this.getFlameChance(iblockdata), i); + } +@@ -338,7 +350,7 @@ public class BlockFire extends BlockFireAbstract { + + @Override + protected boolean e(IBlockData iblockdata) { +- return this.getFlameChance(iblockdata) > 0; ++ return iblockdata != null && this.getFlameChance(iblockdata) > 0; // Paper - iblockdata can be nullable if chunk is unloaded now + } + + @Override diff --git a/patches/server-unmapped/0001/0092-Implement-PlayerLocaleChangeEvent.patch b/patches/server-unmapped/0001/0092-Implement-PlayerLocaleChangeEvent.patch new file mode 100644 index 0000000000..cea882e03a --- /dev/null +++ b/patches/server-unmapped/0001/0092-Implement-PlayerLocaleChangeEvent.patch @@ -0,0 +1,48 @@ +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/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index ad55212370e3d814a397680927a1514ea0fe85b5..120cad4e7330900fa11d278cf87a6ab4484469c3 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -1686,7 +1686,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + return s; + } + +- public String locale = "en_us"; // CraftBukkit - add, lowercase ++ public String locale = null; // CraftBukkit - add, lowercase // Paper - default to null + public java.util.Locale adventure$locale = java.util.Locale.US; // Paper + public void a(PacketPlayInSettings packetplayinsettings) { + // CraftBukkit start +@@ -1694,9 +1694,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(getBukkitEntity(), getMainHand() == EnumMainHand.LEFT ? MainHand.LEFT : MainHand.RIGHT); + this.server.server.getPluginManager().callEvent(event); + } +- if (!this.locale.equals(packetplayinsettings.locale)) { ++ if (this.locale == null || !this.locale.equals(packetplayinsettings.locale)) { // Paper - check for null + PlayerLocaleChangeEvent event = new PlayerLocaleChangeEvent(getBukkitEntity(), packetplayinsettings.locale); + this.server.server.getPluginManager().callEvent(event); ++ new com.destroystokyo.paper.event.player.PlayerLocaleChangeEvent(this.getBukkitEntity(), this.locale, packetplayinsettings.locale).callEvent(); // Paper + } + this.locale = packetplayinsettings.locale; + // Paper start +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index facf2a79144a2d0fbea907c084becaea7bbdc48c..6d7f1dee9ae2fb0b9620d85969de86eee09020cc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1874,8 +1874,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // Paper end + @Override + public String getLocale() { +- return getHandle().locale; +- ++ // Paper start - Locale change event ++ final String locale = getHandle().locale; ++ return locale != null ? locale : "en_us"; ++ // Paper end + } + + // Paper start diff --git a/patches/server-unmapped/0001/0093-EntityRegainHealthEvent-isFastRegen-API.patch b/patches/server-unmapped/0001/0093-EntityRegainHealthEvent-isFastRegen-API.patch new file mode 100644 index 0000000000..ac155999d2 --- /dev/null +++ b/patches/server-unmapped/0001/0093-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/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index c7b40800343edb2c2a68786afb828c9dc3e3627f..dc715a0275b148c3c66610d6fb873626180c82d5 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -1130,10 +1130,16 @@ public abstract class EntityLiving extends Entity { + } + + 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.world.getServer().getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/world/food/FoodMetaData.java b/src/main/java/net/minecraft/world/food/FoodMetaData.java +index 7ed321acba6d46159f7d67b8d10a0a3e06ac88a9..e455b25b0809af15f6fde957121d0110da7eb08f 100644 +--- a/src/main/java/net/minecraft/world/food/FoodMetaData.java ++++ b/src/main/java/net/minecraft/world/food/FoodMetaData.java +@@ -87,7 +87,7 @@ public class FoodMetaData { + if (this.foodTickTimer >= this.saturatedRegenRate) { // CraftBukkit + float f = Math.min(this.saturationLevel, 6.0F); + +- entityhuman.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED); // CraftBukkit - added RegainReason ++ entityhuman.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED, true); // CraftBukkit - added RegainReason // Paper - This is fast regen + // this.a(f); CraftBukkit - EntityExhaustionEvent + entityhuman.applyExhaustion(f, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.REGEN); // CraftBukkit - EntityExhaustionEvent + this.foodTickTimer = 0; diff --git a/patches/server-unmapped/0001/0094-Add-ability-to-configure-frosted_ice-properties.patch b/patches/server-unmapped/0001/0094-Add-ability-to-configure-frosted_ice-properties.patch new file mode 100644 index 0000000000..c48a22b74f --- /dev/null +++ b/patches/server-unmapped/0001/0094-Add-ability-to-configure-frosted_ice-properties.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Thu, 21 Apr 2016 23:51:55 -0700 +Subject: [PATCH] Add ability to configure frosted_ice properties + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 1942f5224aaebb18adb591d6f70a419cfc1a7bdd..5baccb8d50c135ab20c38ffd0690f585514ce5af 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -259,4 +259,14 @@ public class PaperWorldConfig { + private void useVanillaScoreboardColoring() { + useVanillaScoreboardColoring = getBoolean("use-vanilla-world-scoreboard-name-coloring", false); + } ++ ++ public boolean frostedIceEnabled = true; ++ public int frostedIceDelayMin = 20; ++ public int frostedIceDelayMax = 40; ++ private void frostedIce() { ++ this.frostedIceEnabled = this.getBoolean("frosted-ice.enabled", this.frostedIceEnabled); ++ this.frostedIceDelayMin = this.getInt("frosted-ice.delay.min", this.frostedIceDelayMin); ++ this.frostedIceDelayMax = this.getInt("frosted-ice.delay.max", this.frostedIceDelayMax); ++ log("Frosted Ice: " + (this.frostedIceEnabled ? "enabled" : "disabled") + " / delay: min=" + this.frostedIceDelayMin + ", max=" + this.frostedIceDelayMax); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/BlockIceFrost.java b/src/main/java/net/minecraft/world/level/block/BlockIceFrost.java +index 7239a30bd4a5dc4ed09802eea8f7126485ebb635..e32e94868386ff06ff29254e6cc3bee9b446a293 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockIceFrost.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockIceFrost.java +@@ -30,6 +30,7 @@ public class BlockIceFrost extends BlockIce { + + @Override + public void tickAlways(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, Random random) { ++ if (!worldserver.paperConfig.frostedIceEnabled) return; // Paper - add ability to disable frosted ice + if ((random.nextInt(3) == 0 || this.a(worldserver, blockposition, 4)) && worldserver.getLightLevel(blockposition) > 11 - (Integer) iblockdata.get(BlockIceFrost.a) - iblockdata.b((IBlockAccess) worldserver, blockposition) && this.e(iblockdata, (World) worldserver, blockposition)) { + BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); + EnumDirection[] aenumdirection = EnumDirection.values(); +@@ -42,12 +43,12 @@ public class BlockIceFrost extends BlockIce { + IBlockData iblockdata1 = worldserver.getType(blockposition_mutableblockposition); + + if (iblockdata1.a((Block) this) && !this.e(iblockdata1, (World) worldserver, blockposition_mutableblockposition)) { +- worldserver.getBlockTickList().a(blockposition_mutableblockposition, this, MathHelper.nextInt(random, 20, 40)); ++ worldserver.getBlockTickList().a(blockposition_mutableblockposition, this, MathHelper.nextInt(random, worldserver.paperConfig.frostedIceDelayMin, worldserver.paperConfig.frostedIceDelayMax)); // Paper - use configurable min/max delay + } + } + + } else { +- worldserver.getBlockTickList().a(blockposition, this, MathHelper.nextInt(random, 20, 40)); ++ worldserver.getBlockTickList().a(blockposition, this, MathHelper.nextInt(random, worldserver.paperConfig.frostedIceDelayMin, worldserver.paperConfig.frostedIceDelayMax)); // Paper - use configurable min/max delay + } + } + diff --git a/patches/server-unmapped/0001/0095-remove-null-possibility-for-getServer-singleton.patch b/patches/server-unmapped/0001/0095-remove-null-possibility-for-getServer-singleton.patch new file mode 100644 index 0000000000..f215104b52 --- /dev/null +++ b/patches/server-unmapped/0001/0095-remove-null-possibility-for-getServer-singleton.patch @@ -0,0 +1,36 @@ +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 026ddfba26439a00685f3962084aa6194086c9b7..f990f242a8d812a93b454b065a17fd4e8170355a 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -180,6 +180,7 @@ import org.spigotmc.SlackActivityAccountant; // Spigot + + public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant implements IMojangStatistics, ICommandListener, AutoCloseable { + ++ private static MinecraftServer SERVER; // Paper + public static final Logger LOGGER = LogManager.getLogger(); + public static final File b = new File("usercache.json"); + public static final WorldSettings c = new WorldSettings("Demo World", EnumGamemode.SURVIVAL, false, EnumDifficulty.NORMAL, false, new GameRules(), DataPackConfiguration.a); +@@ -286,6 +287,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +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/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 2412c2fa22abe171254f7fe49d319bcd6cc533ff..c4bbc4e97ee1871ed6e4364c1fe9204b0dd2fdae 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1170,6 +1170,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + { + if ( iter.next().trackee == entity ) + { ++ map.decorations.remove(entity.getDisplayName().getString()); // Paper + iter.remove(); + } + } +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index f42e16589476c1bd10b13214dda5ac7bb3e52131..e3e3426a00128b56d523bb43a59b814b915ad0ff 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -86,6 +86,7 @@ import net.minecraft.world.item.ItemElytra; + import net.minecraft.world.item.ItemProjectileWeapon; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.ItemSword; ++import net.minecraft.world.item.ItemWorldMap; + import net.minecraft.world.item.Items; + import net.minecraft.world.item.crafting.IRecipe; + import net.minecraft.world.item.enchantment.EnchantmentManager; +@@ -104,6 +105,7 @@ import net.minecraft.world.level.block.entity.TileEntitySign; + import net.minecraft.world.level.block.entity.TileEntityStructure; + import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.level.block.state.pattern.ShapeDetectorBlock; ++import net.minecraft.world.level.saveddata.maps.WorldMap; + import net.minecraft.world.phys.AxisAlignedBB; + import net.minecraft.world.phys.Vec3D; + import net.minecraft.world.scores.Scoreboard; +@@ -689,6 +691,12 @@ public abstract class EntityHuman extends EntityLiving { + return null; + } + // CraftBukkit end ++ // Paper start - remove player from map on drop ++ if (itemstack.getItem() == Items.FILLED_MAP) { ++ WorldMap worldmap = ItemWorldMap.getSavedMap(itemstack, this.world); ++ worldmap.updateSeenPlayers(this, itemstack); ++ } ++ // Paper end + + return entityitem; + } +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java b/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java +index 3f057f0bd23bc1c693c8f04ee8acd6626c620008..d470af1814af332595c1c0beb1cdc552e186a6bb 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java +@@ -57,6 +57,7 @@ public class WorldMap extends PersistentBase { + private final Map m = Maps.newHashMap(); + public final Map decorations = Maps.newLinkedHashMap(); + private final Map n = Maps.newHashMap(); ++ private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper + + // CraftBukkit start + public final CraftMapView mapView; +@@ -69,6 +70,7 @@ public class WorldMap extends PersistentBase { + // CraftBukkit start + mapView = new CraftMapView(this); + server = (CraftServer) org.bukkit.Bukkit.getServer(); ++ vanillaRender.buffer = colors; // Paper + // CraftBukkit end + } + +@@ -136,6 +138,7 @@ public class WorldMap extends PersistentBase { + this.m.put(mapiconbanner.f(), mapiconbanner); + this.a(mapiconbanner.c(), (GeneratorAccess) null, mapiconbanner.f(), (double) mapiconbanner.a().getX(), (double) mapiconbanner.a().getZ(), 180.0D, mapiconbanner.d()); + } ++ this.vanillaRender.buffer = colors; // Paper + + NBTTagList nbttaglist1 = nbttagcompound.getList("frames", 10); + +@@ -216,6 +219,7 @@ public class WorldMap extends PersistentBase { + this.b(); + } + ++ public void updateSeenPlayers(EntityHuman entityhuman, ItemStack itemstack) { this.a(entityhuman, itemstack); } // Paper - OBFHELPER + public void a(EntityHuman entityhuman, ItemStack itemstack) { + if (!this.humans.containsKey(entityhuman)) { + WorldMap.WorldMapHumanTracker worldmap_worldmaphumantracker = new WorldMap.WorldMapHumanTracker(entityhuman); +@@ -451,6 +455,21 @@ public class WorldMap extends PersistentBase { + + public class WorldMapHumanTracker { + ++ // Paper start ++ private void addSeenPlayers(java.util.Collection icons) { ++ org.bukkit.entity.Player player = (org.bukkit.entity.Player) trackee.getBukkitEntity(); ++ WorldMap.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 EntityHuman trackee; + private boolean d = true; + private int e; +@@ -467,9 +486,12 @@ public class WorldMap extends PersistentBase { + @Nullable + public Packet a(ItemStack itemstack) { + // CraftBukkit start +- org.bukkit.craftbukkit.map.RenderData render = WorldMap.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.trackee.getBukkitEntity()); // CraftBukkit ++ if (!this.d && this.i % 5 != 0) { this.i++; 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 ? WorldMap.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.trackee.getBukkitEntity()) : WorldMap.this.vanillaRender; // CraftBukkit // Paper + + java.util.Collection icons = new java.util.ArrayList(); ++ if (vanillaMaps) addSeenPlayers(icons); // Paper + + for ( org.bukkit.map.MapCursor cursor : render.cursors) { + +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-unmapped/0001/0097-LootTable-API-Replenishable-Lootables-Feature.patch b/patches/server-unmapped/0001/0097-LootTable-API-Replenishable-Lootables-Feature.patch new file mode 100644 index 0000000000..87221d0c29 --- /dev/null +++ b/patches/server-unmapped/0001/0097-LootTable-API-Replenishable-Lootables-Feature.patch @@ -0,0 +1,736 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 1 May 2016 21:19:14 -0400 +Subject: [PATCH] LootTable API & Replenishable Lootables Feature + +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" + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 5baccb8d50c135ab20c38ffd0690f585514ce5af..eb04fdb172a50ec1f5b7fe78fa0e7655246abd60 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -269,4 +269,26 @@ public class PaperWorldConfig { + this.frostedIceDelayMax = this.getInt("frosted-ice.delay.max", this.frostedIceDelayMax); + log("Frosted Ice: " + (this.frostedIceEnabled ? "enabled" : "disabled") + " / delay: min=" + this.frostedIceDelayMin + ", max=" + this.frostedIceDelayMax); + } ++ ++ public boolean autoReplenishLootables; ++ public boolean restrictPlayerReloot; ++ public boolean changeLootTableSeedOnFill; ++ public int maxLootableRefills; ++ public int lootableRegenMin; ++ public int lootableRegenMax; ++ private void enhancedLootables() { ++ autoReplenishLootables = getBoolean("lootables.auto-replenish", false); ++ restrictPlayerReloot = getBoolean("lootables.restrict-player-reloot", true); ++ changeLootTableSeedOnFill = getBoolean("lootables.reset-seed-on-fill", true); ++ maxLootableRefills = getInt("lootables.max-refills", -1); ++ lootableRegenMin = PaperConfig.getSeconds(getString("lootables.refresh-min", "12h")); ++ lootableRegenMax = PaperConfig.getSeconds(getString("lootables.refresh-max", "2d")); ++ if (autoReplenishLootables) { ++ log("Lootables: Replenishing every " + ++ PaperConfig.timeSummary(lootableRegenMin) + " to " + ++ PaperConfig.timeSummary(lootableRegenMax) + ++ (restrictPlayerReloot ? " (restricting reloot)" : "") ++ ); ++ } ++ } + } +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..5dfc3c8008d64ad4ed71b4904c897f5005491349 +--- /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.BlockPosition; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.entity.TileEntityLootable; ++import org.bukkit.Chunk; ++import org.bukkit.block.Block; ++ ++public interface PaperLootableBlockInventory extends LootableBlockInventory, PaperLootableInventory { ++ ++ TileEntityLootable getTileEntity(); ++ ++ @Override ++ default LootableInventory getAPILootableInventory() { ++ return this; ++ } ++ ++ @Override ++ default World getNMSWorld() { ++ return getTileEntity().getWorld(); ++ } ++ ++ default Block getBlock() { ++ final BlockPosition position = getTileEntity().getPosition(); ++ final Chunk bukkitChunk = getTileEntity().getWorld().getChunkAtWorldCoords(position).bukkitChunk; ++ return bukkitChunk.getBlock(position.getX(), position.getY(), position.getZ()); ++ } ++ ++ @Override ++ default PaperLootableInventoryData getLootableData() { ++ return 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..019a06fa2b43cacd3bbd4d58aba71b3728f37581 +--- /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.World; ++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 World getNMSWorld() { ++ return getHandle().getWorld(); ++ } ++ ++ @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..59e8aea749bbba079e3304d9a5854280db2692e9 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java +@@ -0,0 +1,71 @@ ++package com.destroystokyo.paper.loottable; ++ ++import net.minecraft.world.level.World; ++import org.bukkit.loot.Lootable; ++ ++import java.util.UUID; ++ ++public interface PaperLootableInventory extends LootableInventory, Lootable { ++ ++ PaperLootableInventoryData getLootableData(); ++ LootableInventory getAPILootableInventory(); ++ ++ World getNMSWorld(); ++ ++ default org.bukkit.World getBukkitWorld() { ++ return getNMSWorld().getWorld(); ++ } ++ ++ @Override ++ default boolean isRefillEnabled() { ++ return getNMSWorld().paperConfig.autoReplenishLootables; ++ } ++ ++ @Override ++ default boolean hasBeenFilled() { ++ return getLastFilled() != -1; ++ } ++ ++ @Override ++ default boolean hasPlayerLooted(UUID player) { ++ return getLootableData().hasPlayerLooted(player); ++ } ++ ++ @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..904332454ede006f4ee33337d46b11674d78bef7 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java +@@ -0,0 +1,181 @@ ++package com.destroystokyo.paper.loottable; ++ ++import com.destroystokyo.paper.PaperWorldConfig; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.world.entity.player.EntityHuman; ++import org.bukkit.entity.Player; ++import org.bukkit.loot.LootTable; ++ ++import javax.annotation.Nullable; ++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 EntityHuman 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.autoReplenishLootables) { ++ 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 PaperWorldConfig paperConfig = this.lootable.getNMSWorld().paperConfig; ++ ++ // Check if max refills has been hit ++ if (paperConfig.maxLootableRefills != -1 && this.numRefills >= paperConfig.maxLootableRefills) { ++ 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()); ++ if (paperConfig.restrictPlayerReloot && hasPlayerLooted(player.getUniqueID())) { ++ event.setCancelled(true); ++ } ++ return event.callEvent(); ++ } ++ public void processRefill(@Nullable EntityHuman player) { ++ this.lastFill = System.currentTimeMillis(); ++ final PaperWorldConfig paperConfig = this.lootable.getNMSWorld().paperConfig; ++ if (paperConfig.autoReplenishLootables) { ++ int min = paperConfig.lootableRegenMin; ++ int max = paperConfig.lootableRegenMax; ++ this.nextRefill = this.lastFill + (min + RANDOM.nextInt(max - min + 1)) * 1000L; ++ this.numRefills++; ++ if (paperConfig.changeLootTableSeedOnFill) { ++ 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.getUniqueID(), true); ++ } ++ } else { ++ this.lootable.clearLootTable(); ++ } ++ } ++ ++ ++ public void loadNbt(NBTTagCompound base) { ++ if (!base.hasKeyOfType("Paper.LootableData", 10)) { // 10 = compound ++ return; ++ } ++ NBTTagCompound comp = base.getCompound("Paper.LootableData"); ++ if (comp.hasKey("lastFill")) { ++ this.lastFill = comp.getLong("lastFill"); ++ } ++ if (comp.hasKey("nextRefill")) { ++ this.nextRefill = comp.getLong("nextRefill"); ++ } ++ ++ if (comp.hasKey("numRefills")) { ++ this.numRefills = comp.getInt("numRefills"); ++ } ++ if (comp.hasKeyOfType("lootedPlayers", 9)) { // 9 = list ++ NBTTagList list = comp.getList("lootedPlayers", 10); // 10 = compound ++ final int size = list.size(); ++ if (size > 0) { ++ this.lootedPlayers = new HashMap<>(list.size()); ++ } ++ for (int i = 0; i < size; i++) { ++ final NBTTagCompound cmp = list.getCompound(i); ++ lootedPlayers.put(cmp.getUUID("UUID"), cmp.getLong("Time")); ++ } ++ } ++ } ++ public void saveNbt(NBTTagCompound base) { ++ NBTTagCompound comp = new NBTTagCompound(); ++ if (this.nextRefill != -1) { ++ comp.setLong("nextRefill", this.nextRefill); ++ } ++ if (this.lastFill != -1) { ++ comp.setLong("lastFill", this.lastFill); ++ } ++ if (this.numRefills != 0) { ++ comp.setInt("numRefills", this.numRefills); ++ } ++ if (this.lootedPlayers != null && !this.lootedPlayers.isEmpty()) { ++ NBTTagList list = new NBTTagList(); ++ for (Map.Entry entry : this.lootedPlayers.entrySet()) { ++ NBTTagCompound cmp = new NBTTagCompound(); ++ cmp.setUUID("UUID", entry.getKey()); ++ cmp.setLong("Time", entry.getValue()); ++ list.add(cmp); ++ } ++ comp.set("lootedPlayers", list); ++ } ++ ++ if (!comp.isEmpty()) { ++ base.set("Paper.LootableData", comp); ++ } ++ } ++ ++ void setPlayerLootedState(UUID player, boolean looted) { ++ if (looted && this.lootedPlayers == null) { ++ this.lootedPlayers = new HashMap<>(); ++ } ++ if (looted) { ++ if (!this.lootedPlayers.containsKey(player)) { ++ this.lootedPlayers.put(player, System.currentTimeMillis()); ++ } ++ } else if (this.lootedPlayers != null) { ++ this.lootedPlayers.remove(player); ++ } ++ } ++ ++ 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/PaperMinecartLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperMinecartLootableInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c682bd7700d8103533026d46cfc63a7abde5a5f4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperMinecartLootableInventory.java +@@ -0,0 +1,62 @@ ++package com.destroystokyo.paper.loottable; ++ ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.vehicle.EntityMinecartContainer; ++import net.minecraft.world.level.World; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++ ++public class PaperMinecartLootableInventory implements PaperLootableEntityInventory { ++ ++ private EntityMinecartContainer entity; ++ ++ public PaperMinecartLootableInventory(EntityMinecartContainer entity) { ++ this.entity = entity; ++ } ++ ++ @Override ++ public org.bukkit.loot.LootTable getLootTable() { ++ return entity.lootTable != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(entity.lootTable)) : null; ++ } ++ ++ @Override ++ public void setLootTable(org.bukkit.loot.LootTable table, long seed) { ++ setLootTable(table); ++ setSeed(seed); ++ } ++ ++ @Override ++ public void setSeed(long seed) { ++ entity.lootTableSeed = seed; ++ } ++ ++ @Override ++ public long getSeed() { ++ return entity.lootTableSeed; ++ } ++ ++ @Override ++ public void setLootTable(org.bukkit.loot.LootTable table) { ++ entity.lootTable = (table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey()); ++ } ++ ++ @Override ++ public PaperLootableInventoryData getLootableData() { ++ return entity.lootableData; ++ } ++ ++ @Override ++ public Entity getHandle() { ++ return entity; ++ } ++ ++ @Override ++ public LootableInventory getAPILootableInventory() { ++ return (LootableInventory) entity.getBukkitEntity(); ++ } ++ ++ @Override ++ public World getNMSWorld() { ++ return entity.world; ++ } ++} +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..9dae34370d014a291f025f83b55e18bff4619a23 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java +@@ -0,0 +1,65 @@ ++package com.destroystokyo.paper.loottable; ++ ++import net.minecraft.server.MCUtil; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.entity.TileEntityLootable; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++ ++public class PaperTileEntityLootableInventory implements PaperLootableBlockInventory { ++ private TileEntityLootable tileEntityLootable; ++ ++ public PaperTileEntityLootableInventory(TileEntityLootable 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 TileEntityLootable getTileEntity() { ++ return tileEntityLootable; ++ } ++ ++ @Override ++ public LootableInventory getAPILootableInventory() { ++ World world = tileEntityLootable.getWorld(); ++ if (world == null) { ++ return null; ++ } ++ return (LootableInventory) getBukkitWorld().getBlockAt(MCUtil.toLocation(world, tileEntityLootable.getPosition())).getState(); ++ } ++ ++ @Override ++ public World getNMSWorld() { ++ return tileEntityLootable.getWorld(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index e67c7f05620a4c76b4f806c11065f6e1938f0903..f942d75982409f7640f073f9c77f8939225c6939 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -158,6 +158,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + }; + // Paper end + ++ 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/EntityMinecartContainer.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartContainer.java +index c4b970c37b1792ac0022936f2df4740183621a0d..0166d11cb540a536390f486e1069d6119d8d23d6 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartContainer.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartContainer.java +@@ -46,6 +46,7 @@ public abstract class EntityMinecartContainer extends EntityMinecartAbstract imp + public long lootTableSeed; + + // CraftBukkit start ++ { this.lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperMinecartLootableInventory(this)); } // Paper + public List transaction = new java.util.ArrayList(); + private int maxStack = MAX_STACK; + +@@ -203,12 +204,13 @@ public abstract class EntityMinecartContainer extends EntityMinecartAbstract imp + @Override + protected void saveData(NBTTagCompound nbttagcompound) { + super.saveData(nbttagcompound); ++ this.lootableData.saveNbt(nbttagcompound); // Paper + if (this.lootTable != null) { + nbttagcompound.setString("LootTable", this.lootTable.toString()); + if (this.lootTableSeed != 0L) { + nbttagcompound.setLong("LootTableSeed", this.lootTableSeed); + } +- } else { ++ } if (true) { // Paper - Always save the items, Table may stick around + ContainerUtil.a(nbttagcompound, this.items); + } + +@@ -217,11 +219,12 @@ public abstract class EntityMinecartContainer extends EntityMinecartAbstract imp + @Override + protected void loadData(NBTTagCompound nbttagcompound) { + super.loadData(nbttagcompound); ++ this.lootableData.loadNbt(nbttagcompound); // Paper + this.items = NonNullList.a(this.getSize(), ItemStack.b); + if (nbttagcompound.hasKeyOfType("LootTable", 8)) { + this.lootTable = new MinecraftKey(nbttagcompound.getString("LootTable")); + this.lootTableSeed = nbttagcompound.getLong("LootTableSeed"); +- } else { ++ } if (true) { // Paper - always load the items, table may still remain + ContainerUtil.b(nbttagcompound, this.items); + } + +@@ -252,14 +255,15 @@ public abstract class EntityMinecartContainer extends EntityMinecartAbstract imp + } + + public void d(@Nullable EntityHuman entityhuman) { +- if (this.lootTable != null && this.world.getMinecraftServer() != null) { ++ if (this.lootableData.shouldReplenish(entityhuman) && this.world.getMinecraftServer() != null) { // Paper + LootTable loottable = this.world.getMinecraftServer().getLootTableRegistry().getLootTable(this.lootTable); + + if (entityhuman instanceof EntityPlayer) { + CriterionTriggers.N.a((EntityPlayer) entityhuman, this.lootTable); + } + +- this.lootTable = null; ++ //this.lootTable = null; // Paper ++ this.lootableData.processRefill(entityhuman); // Paper + LootTableInfo.Builder loottableinfo_builder = (new LootTableInfo.Builder((WorldServer) this.world)).set(LootContextParameters.ORIGIN, this.getPositionVector()).a(this.lootTableSeed); + + if (entityhuman != null) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityLootable.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityLootable.java +index 62e6833a90d7adae3c7df33e3bc73b4288e0370b..1508e267a38555820e2d31f3075adca185fbd4b6 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityLootable.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityLootable.java +@@ -27,6 +27,7 @@ public abstract class TileEntityLootable extends TileEntityContainer { + @Nullable + public MinecraftKey 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 TileEntityLootable(TileEntityTypes tileentitytypes) { + super(tileentitytypes); +@@ -42,16 +43,19 @@ public abstract class TileEntityLootable extends TileEntityContainer { + } + + protected boolean b(NBTTagCompound nbttagcompound) { ++ this.lootableData.loadNbt(nbttagcompound); // Paper + if (nbttagcompound.hasKeyOfType("LootTable", 8)) { + this.lootTable = new MinecraftKey(nbttagcompound.getString("LootTable")); ++ try { org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.lootTable); } catch (IllegalArgumentException ex) { this.lootTable = null; } // Paper - validate + this.lootTableSeed = nbttagcompound.getLong("LootTableSeed"); +- return true; ++ return false; // Paper - always load the items, table may still remain + } else { + return false; + } + } + + protected boolean c(NBTTagCompound nbttagcompound) { ++ this.lootableData.saveNbt(nbttagcompound); // Paper + if (this.lootTable == null) { + return false; + } else { +@@ -60,19 +64,20 @@ public abstract class TileEntityLootable extends TileEntityContainer { + nbttagcompound.setLong("LootTableSeed", this.lootTableSeed); + } + +- return true; ++ return false; // Paper - always save the items, table may still remain + } + } + + public void d(@Nullable EntityHuman entityhuman) { +- if (this.lootTable != null && this.world.getMinecraftServer() != null) { ++ if (this.lootableData.shouldReplenish(entityhuman) && this.world.getMinecraftServer() != null) { // Paper + LootTable loottable = this.world.getMinecraftServer().getLootTableRegistry().getLootTable(this.lootTable); + + if (entityhuman instanceof EntityPlayer) { + CriterionTriggers.N.a((EntityPlayer) entityhuman, this.lootTable); + } + +- this.lootTable = null; ++ //this.lootTable = null; // Paper ++ this.lootableData.processRefill(entityhuman); // Paper + LootTableInfo.Builder loottableinfo_builder = (new LootTableInfo.Builder((WorldServer) this.world)).set(LootContextParameters.ORIGIN, Vec3D.a((BaseBlockPosition) this.position)).a(this.lootTableSeed); + + if (entityhuman != null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index 524f27830752f424493c3ae8d793b871f6495594..dcf3f9265b0b00a7bbb9ff428e10da3c198ba08a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -64,7 +64,7 @@ public class CraftBlockEntityState extends CraftBlockState + } + + // gets the wrapped TileEntity +- protected T getTileEntity() { ++ public T getTileEntity() { // Paper - protected -> public + return tileEntity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +index 486fa8937d644f59a770db163482259525a7e465..54eb170fd533b0e91572601268fcbc167ed9bb5c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +@@ -12,8 +12,9 @@ import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.inventory.CraftInventory; + import org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest; + import org.bukkit.inventory.Inventory; ++import com.destroystokyo.paper.loottable.PaperLootableBlockInventory; // Paper + +-public class CraftChest extends CraftLootable implements Chest { ++public class CraftChest extends CraftLootable implements Chest, PaperLootableBlockInventory { // Paper + + public CraftChest(final Block block) { + super(block, TileEntityChest.class); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java +index f0a7e61a26c4668a9aa823d641f29bdecd42dd1f..3512054ede5fd1dd7605444e827e30a0be47f935 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java +@@ -10,7 +10,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(Block block, Class tileEntityClass) { + super(block, tileEntityClass); +@@ -54,7 +54,7 @@ public abstract class CraftLootable extends CraftC + setLootTable(getLootTable(), seed); + } + +- private void setLootTable(LootTable table, long seed) { ++ public void setLootTable(LootTable table, long seed) { // Paper - public + MinecraftKey key = (table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey()); + getSnapshot().setLootTable(key, seed); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java +index cbd121c21adfaf098dadca33de16a2e68d83c19a..d9a2552782c9242fb84cc0c8309a614a44777509 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java +@@ -8,7 +8,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, EntityMinecartChest entity) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java +index 5ffb8108f456c2f7f3ed1a25249baccb4cbf4add..bf8b5b25d1af0c5129261e10abf2866521b2c375 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java +@@ -47,7 +47,7 @@ public abstract class CraftMinecartContainer extends CraftMinecart implements Lo + return getHandle().lootTableSeed; + } + +- private void setLootTable(LootTable table, long seed) { ++ public void setLootTable(LootTable table, long seed) { // Paper + MinecraftKey newKey = (table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey()); + getHandle().setLootTable(newKey, seed); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +index 17a42aec76f32a28b0c9885c60d1ed50c6727161..bfdcf01d2c6570493e86330d56500427dbb23146 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +@@ -7,7 +7,7 @@ import org.bukkit.entity.EntityType; + 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, EntityMinecartHopper entity) { diff --git a/patches/server-unmapped/0001/0098-Don-t-save-empty-scoreboard-teams-to-scoreboard.dat.patch b/patches/server-unmapped/0001/0098-Don-t-save-empty-scoreboard-teams-to-scoreboard.dat.patch new file mode 100644 index 0000000000..a61442ff71 --- /dev/null +++ b/patches/server-unmapped/0001/0098-Don-t-save-empty-scoreboard-teams-to-scoreboard.dat.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 7 May 2016 23:33:08 -0400 +Subject: [PATCH] Don't save empty scoreboard teams to scoreboard.dat + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 504efea7b6f50a0d17f4f353781953dfb18bdeca..1b8e5671c9dc8c15ce33d351c1bb20f28919b9a2 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -237,4 +237,9 @@ public class PaperConfig { + private static void enablePlayerCollisions() { + enablePlayerCollisions = getBoolean("settings.enable-player-collisions", true); + } ++ ++ public static boolean saveEmptyScoreboardTeams = false; ++ private static void saveEmptyScoreboardTeams() { ++ saveEmptyScoreboardTeams = getBoolean("settings.save-empty-scoreboard-teams", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/scores/PersistentScoreboard.java b/src/main/java/net/minecraft/world/scores/PersistentScoreboard.java +index 3998565ccd87c966c0fb9e6757cd1861faa5bc15..52f27bdbd0df8bbbf2ad5144bc262b5093d83413 100644 +--- a/src/main/java/net/minecraft/world/scores/PersistentScoreboard.java ++++ b/src/main/java/net/minecraft/world/scores/PersistentScoreboard.java +@@ -182,6 +182,7 @@ public class PersistentScoreboard extends PersistentBase { + + while (iterator.hasNext()) { + ScoreboardTeam scoreboardteam = (ScoreboardTeam) iterator.next(); ++ if (!com.destroystokyo.paper.PaperConfig.saveEmptyScoreboardTeams && scoreboardteam.getPlayerNameSet().isEmpty()) continue; // Paper + NBTTagCompound nbttagcompound = new NBTTagCompound(); + + nbttagcompound.setString("Name", scoreboardteam.getName()); diff --git a/patches/server-unmapped/0001/0099-System-property-for-disabling-watchdoge.patch b/patches/server-unmapped/0001/0099-System-property-for-disabling-watchdoge.patch new file mode 100644 index 0000000000..1cbedf26ca --- /dev/null +++ b/patches/server-unmapped/0001/0099-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 69e5054886b5858664fed333aca8c25a76e5cb11..4e0291be4bd5876bb5b5f62ebfa156635d4c758f 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 ( !stopping ) + { + // +- if ( lastTick != 0 && timeoutTime > 0 && monotonicMillis() > lastTick + timeoutTime ) ++ if ( lastTick != 0 && timeoutTime > 0 && monotonicMillis() > lastTick + timeoutTime && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable + { + Logger log = Bukkit.getServer().getLogger(); + log.log( Level.SEVERE, "------------------------------" ); diff --git a/patches/server-unmapped/0001/0100-Optimize-UserCache-Thread-Safe.patch b/patches/server-unmapped/0001/0100-Optimize-UserCache-Thread-Safe.patch new file mode 100644 index 0000000000..3f1918cdd8 --- /dev/null +++ b/patches/server-unmapped/0001/0100-Optimize-UserCache-Thread-Safe.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 16 May 2016 20:47:41 -0400 +Subject: [PATCH] Optimize UserCache / Thread Safe + +Because Techable keeps complaining about how this isn't thread safe, +easier to do this than replace the entire thing. + +Additionally, move Saving of the User cache to be done async, incase +the user never changed the default setting for Spigot's save on stop only. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index f990f242a8d812a93b454b065a17fd4e8170355a..283c1111d99b6ae09b6db0c0079eeb0f1cbb7b2b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -907,7 +907,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { // Paper + + try { + BufferedWriter bufferedwriter = Files.newWriter(this.g, StandardCharsets.UTF_8); +@@ -268,6 +270,14 @@ public class UserCache { + } catch (IOException ioexception) { + ; + } ++ // Paper start ++ }; ++ if (asyncSave) { ++ MCUtil.scheduleAsyncTask(save); ++ } else { ++ save.run(); ++ } ++ // Paper end + + } + diff --git a/patches/server-unmapped/0001/0101-Avoid-blocking-on-Network-Manager-creation.patch b/patches/server-unmapped/0001/0101-Avoid-blocking-on-Network-Manager-creation.patch new file mode 100644 index 0000000000..dd0b2f44b9 --- /dev/null +++ b/patches/server-unmapped/0001/0101-Avoid-blocking-on-Network-Manager-creation.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 16 May 2016 23:19:16 -0400 +Subject: [PATCH] Avoid blocking on Network Manager creation + +Per Paper issue 294 + +diff --git a/src/main/java/net/minecraft/server/network/ServerConnection.java b/src/main/java/net/minecraft/server/network/ServerConnection.java +index f66a5ba901601c1d359a287861a2edd8e3a106a7..d992cb5cd827e0fe655809e1088939cdad9c2301 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnection.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnection.java +@@ -52,6 +52,15 @@ public class ServerConnection { + public volatile boolean c; + private final List listeningChannels = Collections.synchronizedList(Lists.newArrayList()); + private final List connectedChannels = Collections.synchronizedList(Lists.newArrayList()); ++ // Paper start - prevent blocking on adding a new network manager while the server is ticking ++ private final java.util.Queue pending = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private void addPending() { ++ NetworkManager manager = null; ++ while ((manager = pending.poll()) != null) { ++ connectedChannels.add(manager); ++ } ++ } ++ // Paper end + + public ServerConnection(MinecraftServer minecraftserver) { + this.e = minecraftserver; +@@ -87,7 +96,8 @@ public class ServerConnection { + int j = ServerConnection.this.e.k(); + Object object = j > 0 ? new NetworkManagerServer(j) : new NetworkManager(EnumProtocolDirection.SERVERBOUND); + +- ServerConnection.this.connectedChannels.add((NetworkManager) object); // CraftBukkit - decompile error ++ //ServerConnection.this.connectedChannels.add((NetworkManager) object); // CraftBukkit - decompile error ++ pending.add((NetworkManager) object); // Paper + channel.pipeline().addLast("packet_handler", (ChannelHandler) object); + ((NetworkManager) object).setPacketListener(new HandshakeListener(ServerConnection.this.e, (NetworkManager) object)); + } +@@ -126,6 +136,7 @@ public class ServerConnection { + + synchronized (this.connectedChannels) { + // Spigot Start ++ this.addPending(); // Paper + // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order + if ( org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0 ) + { diff --git a/patches/server-unmapped/0001/0102-Optional-TNT-doesn-t-move-in-water.patch b/patches/server-unmapped/0001/0102-Optional-TNT-doesn-t-move-in-water.patch new file mode 100644 index 0000000000..b8539cf80a --- /dev/null +++ b/patches/server-unmapped/0001/0102-Optional-TNT-doesn-t-move-in-water.patch @@ -0,0 +1,122 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sun, 22 May 2016 20:20:55 -0500 +Subject: [PATCH] Optional TNT doesn't move in water + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index eb04fdb172a50ec1f5b7fe78fa0e7655246abd60..6eca3f300020006f02dd36253b522db442e3cc33 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -2,7 +2,6 @@ package com.destroystokyo.paper; + + import java.util.List; + +-import org.bukkit.Bukkit; + import org.bukkit.configuration.file.YamlConfiguration; + import org.spigotmc.SpigotWorldConfig; + +@@ -291,4 +290,14 @@ public class PaperWorldConfig { + ); + } + } ++ ++ public boolean preventTntFromMovingInWater; ++ private void preventTntFromMovingInWater() { ++ if (PaperConfig.version < 13) { ++ boolean oldVal = getBoolean("enable-old-tnt-cannon-behaviors", false); ++ set("prevent-tnt-from-moving-in-water", oldVal); ++ } ++ preventTntFromMovingInWater = getBoolean("prevent-tnt-from-moving-in-water", false); ++ log("Prevent TNT from moving in water: " + preventTntFromMovingInWater); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java +index beb0beb716869978be6bc5a78ce3b6cf785c5aee..e3cdea3c85d762af6984f3dbe544fdfe101f6ff6 100644 +--- a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java ++++ b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java +@@ -67,7 +67,7 @@ public class EntityTrackerEntry { + private boolean q; + private boolean r; + // CraftBukkit start +- private final Set trackedPlayers; ++ final Set trackedPlayers; // Paper - private -> package + // Paper start + private java.util.Map trackedPlayerMap = null; + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f942d75982409f7640f073f9c77f8939225c6939..88ffc594a2ee7f8718337883609ad4c082f85f50 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2766,6 +2766,11 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public boolean bV() { ++ // Paper start ++ return this.pushedByWater(); ++ } ++ public boolean pushedByWater() { ++ // Paper end + return true; + } + +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java b/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java +index 535e7d7297d81026b8586d5049b72fa65519b464..63b35feac07f01b200dd68c4836ceb419e951660 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityTNTPrimed.java +@@ -4,10 +4,14 @@ import javax.annotation.Nullable; + import net.minecraft.core.particles.Particles; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport; ++import net.minecraft.network.protocol.game.PacketPlayOutEntityVelocity; + import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity; + import net.minecraft.network.syncher.DataWatcher; + import net.minecraft.network.syncher.DataWatcherObject; + import net.minecraft.network.syncher.DataWatcherRegistry; ++import net.minecraft.server.level.PlayerChunkMap; ++import net.minecraft.server.level.WorldServer; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityLiving; + import net.minecraft.world.entity.EntityPose; +@@ -96,7 +100,27 @@ public class EntityTNTPrimed extends Entity { + this.world.addParticle(Particles.SMOKE, this.locX(), this.locY() + 0.5D, this.locZ(), 0.0D, 0.0D, 0.0D); + } + } +- ++ // Paper start - Optional prevent TNT from moving in water ++ if (!this.dead && this.inWater && this.world.paperConfig.preventTntFromMovingInWater) { ++ /* ++ * Author: Jedediah Smith ++ */ ++ // Send position and velocity updates to nearby players on every tick while the TNT is in water. ++ // This does pretty well at keeping their clients in sync with the server. ++ PlayerChunkMap.EntityTracker ete = ((WorldServer)this.world).getChunkProvider().playerChunkMap.trackedEntities.get(this.getId()); ++ if (ete != null) { ++ PacketPlayOutEntityVelocity velocityPacket = new PacketPlayOutEntityVelocity(this); ++ PacketPlayOutEntityTeleport positionPacket = new PacketPlayOutEntityTeleport(this); ++ ++ ete.trackedPlayers.stream() ++ .filter(viewer -> (viewer.locX() - this.locX()) * (viewer.locY() - this.locY()) * (viewer.locZ() - this.locZ()) < 16 * 16) ++ .forEach(viewer -> { ++ viewer.playerConnection.sendPacket(velocityPacket); ++ viewer.playerConnection.sendPacket(positionPacket); ++ }); ++ } ++ } ++ // Paper end + } + + private void explode() { +@@ -165,4 +189,11 @@ public class EntityTNTPrimed extends Entity { + public Packet P() { + return new PacketPlayOutSpawnEntity(this); + } ++ ++ // Paper start - Optional prevent TNT from moving in water ++ @Override ++ public boolean pushedByWater() { ++ return !world.paperConfig.preventTntFromMovingInWater && super.pushedByWater(); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0103-Faster-redstone-torch-rapid-clock-removal.patch b/patches/server-unmapped/0001/0103-Faster-redstone-torch-rapid-clock-removal.patch new file mode 100644 index 0000000000..95886ce17d --- /dev/null +++ b/patches/server-unmapped/0001/0103-Faster-redstone-torch-rapid-clock-removal.patch @@ -0,0 +1,98 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Panzer +Date: Mon, 23 May 2016 12:12:37 +0200 +Subject: [PATCH] Faster redstone torch rapid clock removal + +Only resize the the redstone torch list once, since resizing arrays / lists is costly + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 453ae9a0976c309af5bf59cbb785dc56fd7ffe82..46da7af8dd2202c299d8530c5ff570a0baae6bea 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -44,6 +44,7 @@ import net.minecraft.world.level.biome.BiomeBase; + import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.BlockFireAbstract; ++import net.minecraft.world.level.block.BlockRedstoneTorch; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.entity.ITickable; + import net.minecraft.world.level.block.entity.TileEntity; +@@ -142,6 +143,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + private org.spigotmc.TickLimiter tileLimiter; + private int tileTickPosition; + public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions ++ public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Move from Map in BlockRedstoneTorch to here + + public CraftWorld getWorld() { + return this.world; +diff --git a/src/main/java/net/minecraft/world/level/block/BlockRedstoneTorch.java b/src/main/java/net/minecraft/world/level/block/BlockRedstoneTorch.java +index 8142c0be2978d8975612488b17da9c2e25f3b5dd..6771c16b4228c1495950484422b73928f6184929 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockRedstoneTorch.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockRedstoneTorch.java +@@ -22,7 +22,7 @@ import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit + public class BlockRedstoneTorch extends BlockTorch { + + public static final BlockStateBoolean LIT = BlockProperties.r; +- private static final Map> b = new WeakHashMap(); ++ // Paper - Move the mapped list to World + + protected BlockRedstoneTorch(BlockBase.Info blockbase_info) { + super(blockbase_info, ParticleParamRedstone.a); +@@ -69,11 +69,15 @@ public class BlockRedstoneTorch extends BlockTorch { + @Override + public void tickAlways(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, Random random) { + boolean flag = this.a((World) worldserver, blockposition, iblockdata); +- List list = (List) BlockRedstoneTorch.b.get(worldserver); +- +- while (list != null && !list.isEmpty() && worldserver.getTime() - ((BlockRedstoneTorch.RedstoneUpdateInfo) list.get(0)).b > 60L) { +- list.remove(0); ++ // Paper start ++ java.util.ArrayDeque redstoneUpdateInfos = worldserver.redstoneUpdateInfos; ++ if (redstoneUpdateInfos != null) { ++ BlockRedstoneTorch.RedstoneUpdateInfo curr; ++ while ((curr = redstoneUpdateInfos.peek()) != null && worldserver.getTime() - curr.getTime() > 60L) { ++ redstoneUpdateInfos.poll(); ++ } + } ++ // Paper end + + // CraftBukkit start + org.bukkit.plugin.PluginManager manager = worldserver.getServer().getPluginManager(); +@@ -138,9 +142,12 @@ public class BlockRedstoneTorch extends BlockTorch { + } + + private static boolean a(World world, BlockPosition blockposition, boolean flag) { +- List list = (List) BlockRedstoneTorch.b.computeIfAbsent(world, (iblockaccess) -> { +- return Lists.newArrayList(); +- }); ++ // Paper start ++ java.util.ArrayDeque list = world.redstoneUpdateInfos; ++ if (list == null) { ++ list = world.redstoneUpdateInfos = new java.util.ArrayDeque<>(); ++ } ++ + + if (flag) { + list.add(new BlockRedstoneTorch.RedstoneUpdateInfo(blockposition.immutableCopy(), world.getTime())); +@@ -148,9 +155,9 @@ public class BlockRedstoneTorch extends BlockTorch { + + int i = 0; + +- for (int j = 0; j < list.size(); ++j) { +- BlockRedstoneTorch.RedstoneUpdateInfo blockredstonetorch_redstoneupdateinfo = (BlockRedstoneTorch.RedstoneUpdateInfo) list.get(j); +- ++ for (java.util.Iterator iterator = list.iterator(); iterator.hasNext();) { ++ BlockRedstoneTorch.RedstoneUpdateInfo blockredstonetorch_redstoneupdateinfo = iterator.next(); ++ // Paper end + if (blockredstonetorch_redstoneupdateinfo.a.equals(blockposition)) { + ++i; + if (i >= 8) { +@@ -165,7 +172,7 @@ public class BlockRedstoneTorch extends BlockTorch { + public static class RedstoneUpdateInfo { + + private final BlockPosition a; +- private final long b; ++ private final long b; final long getTime() { return this.b; } // Paper - OBFHELPER + + public RedstoneUpdateInfo(BlockPosition blockposition, long i) { + this.a = blockposition; diff --git a/patches/server-unmapped/0001/0104-Add-server-name-parameter.patch b/patches/server-unmapped/0001/0104-Add-server-name-parameter.patch new file mode 100644 index 0000000000..7facb4f642 --- /dev/null +++ b/patches/server-unmapped/0001/0104-Add-server-name-parameter.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martin Panzer +Date: Sat, 28 May 2016 16:54:03 +0200 +Subject: [PATCH] Add server-name parameter + + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 8efd3919bec5f497ce4bee041586c0a313931211..0c069673437d97aff29b3a30caa22fcf62d0d7db 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -137,6 +137,14 @@ public class Main { + .defaultsTo(new File("paper.yml")) + .describedAs("Yml file"); + // Paper end ++ ++ // Paper start ++ acceptsAll(asList("server-name"), "Name of the server") ++ .withRequiredArg() ++ .ofType(String.class) ++ .defaultsTo("Unknown Server") ++ .describedAs("Name"); ++ // Paper end + } + }; + diff --git a/patches/server-unmapped/0001/0105-Only-send-Dragon-Wither-Death-sounds-to-same-world.patch b/patches/server-unmapped/0001/0105-Only-send-Dragon-Wither-Death-sounds-to-same-world.patch new file mode 100644 index 0000000000..0b6b3e6fdb --- /dev/null +++ b/patches/server-unmapped/0001/0105-Only-send-Dragon-Wither-Death-sounds-to-same-world.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 31 May 2016 22:53:50 -0400 +Subject: [PATCH] Only send Dragon/Wither Death sounds to same world + +Also fix view distance lookup + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java +index 6c58cf9d39cc0f0eb0dc7ddb126b8a3cf6a08fe7..74802de01dba30e38e09f6fc1f61e7bb64cf5f09 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java +@@ -619,8 +619,9 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + if (this.deathAnimationTicks == 1 && !this.isSilent()) { + // CraftBukkit start - Use relative location for far away sounds + // this.world.b(1028, this.getChunkCoordinates(), 0); +- int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; +- for (net.minecraft.server.level.EntityPlayer player : this.world.getMinecraftServer().getPlayerList().players) { ++ //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API ++ for (net.minecraft.server.level.EntityPlayer player : (List) ((WorldServer)world).getPlayers()) { ++ final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch + double deltaX = this.locX() - player.locX(); + double deltaZ = this.locZ() - player.locZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java +index f74949c855aea32ceb16d8cb07f266d50045b57e..145767e8b0fc4105a0afa47af17dcdbb75e952bc 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java +@@ -258,8 +258,9 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + if (!this.isSilent()) { + // CraftBukkit start - Use relative location for far away sounds + // this.world.b(1023, new BlockPosition(this), 0); +- int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; +- for (EntityPlayer player : (List) MinecraftServer.getServer().getPlayerList().players) { ++ //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API ++ for (EntityPlayer player : (List)this.world.getPlayers()) { ++ final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch + double deltaX = this.locX() - player.locX(); + double deltaZ = this.locZ() - player.locZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; diff --git a/patches/server-unmapped/0001/0106-Fix-Double-World-Add-issues.patch b/patches/server-unmapped/0001/0106-Fix-Double-World-Add-issues.patch new file mode 100644 index 0000000000..d3f7911b35 --- /dev/null +++ b/patches/server-unmapped/0001/0106-Fix-Double-World-Add-issues.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 21 Jun 2016 22:54:34 -0400 +Subject: [PATCH] Fix Double World Add issues + +Vanilla will double add Spider Jockeys to the world, so ignore already added. + +Also add debug if something else tries to, and abort before world gets bad state + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index c4bbc4e97ee1871ed6e4364c1fe9204b0dd2fdae..e008ef98d6902f5e1000da99870b12ae9d61bddb 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1038,6 +1038,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + // CraftBukkit start + private boolean addEntity0(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { + org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ if (entity.valid) { MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); return true; } // Paper + if (entity.dead) { + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getName(entity.getEntityType())); // CraftBukkit + return false; diff --git a/patches/server-unmapped/0001/0107-Fix-Old-Sign-Conversion.patch b/patches/server-unmapped/0001/0107-Fix-Old-Sign-Conversion.patch new file mode 100644 index 0000000000..e06e531049 --- /dev/null +++ b/patches/server-unmapped/0001/0107-Fix-Old-Sign-Conversion.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 17 Jun 2016 20:50:11 -0400 +Subject: [PATCH] Fix Old Sign Conversion + +1) Sign loading code was trying to parse the JSON before the check for oldSign. + That code could then skip the old sign converting code if it triggers a JSON parse exception. +2) New Mojang Schematic system has Tile Entities in the new converted format, but missing the Bukkit.isConverted flag + This causes Igloos and such to render broken signs. We fix this by ignoring sign conversion for Defined Structures + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +index 58789a6e285c31947508deae37caefe7e182278c..9b44ca96669ce423e5649f11743226dfdd9ce746 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +@@ -34,6 +34,7 @@ public abstract class TileEntity implements net.minecraft.server.KeyedObject { / + public CraftPersistentDataContainer persistentDataContainer; + // CraftBukkit end + private static final Logger LOGGER = LogManager.getLogger(); ++ public boolean isLoadingStructure = false; // Paper + private final TileEntityTypes tileType; public TileEntityTypes getTileEntityType() { return tileType; } // Paper - OBFHELPER + @Nullable + protected World world; +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java +index 29db550d91cf9e5a23052772df6e482a5e2b0b90..ec550aaa4e7943af4ecdd2275f1f32c21edf770a 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java +@@ -78,13 +78,14 @@ public class TileEntitySign extends TileEntity implements ICommandListener { // + } + + try { +- IChatMutableComponent ichatmutablecomponent = IChatBaseComponent.ChatSerializer.a(s.isEmpty() ? "\"\"" : s); ++ //IChatMutableComponent ichatmutablecomponent = IChatBaseComponent.ChatSerializer.a(s.isEmpty() ? "\"\"" : s); // Paper - move down - the old format might throw a json error + +- if (oldSign) { ++ if (oldSign && !isLoadingStructure) { // Paper - saved structures will be in the new format, but will not have isConverted + lines[i] = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(s)[0]; + continue; + } + // CraftBukkit end ++ IChatMutableComponent ichatmutablecomponent = IChatBaseComponent.ChatSerializer.a(s.isEmpty() ? "\"\"" : s); // Paper - after old sign + + if (this.world instanceof WorldServer) { + try { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.java +index a2bc771df054923a9a96c0024a426ef707624359..9b82ff37faaafc3a799413f6949fb88a993aa9a0 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.java +@@ -278,9 +278,11 @@ public class DefinedStructure { + definedstructure_blockinfo.c.setLong("LootTableSeed", random.nextLong()); + } + ++ tileentity.isLoadingStructure = true; // Paper + tileentity.load(definedstructure_blockinfo.b, definedstructure_blockinfo.c); + tileentity.a(definedstructureinfo.c()); + tileentity.a(definedstructureinfo.d()); ++ tileentity.isLoadingStructure = false; // Paper + } + } + diff --git a/patches/server-unmapped/0001/0108-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch b/patches/server-unmapped/0001/0108-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch new file mode 100644 index 0000000000..60ded56167 --- /dev/null +++ b/patches/server-unmapped/0001/0108-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 16 Jul 2016 19:11:17 -0500 +Subject: [PATCH] Don't lookup game profiles that have no UUID and no name + + +diff --git a/src/main/java/net/minecraft/server/players/UserCache.java b/src/main/java/net/minecraft/server/players/UserCache.java +index 39d1c379b781c08bfdd720cd6810a9c0bb9f0d09..4ad084e7cea3b341ca0dbaa6e853cfc685a555ff 100644 +--- a/src/main/java/net/minecraft/server/players/UserCache.java ++++ b/src/main/java/net/minecraft/server/players/UserCache.java +@@ -92,7 +92,7 @@ public class UserCache { + gameprofilerepository.findProfilesByNames(new String[]{s}, Agent.MINECRAFT, profilelookupcallback); + GameProfile gameprofile = (GameProfile) atomicreference.get(); + +- if (!c() && gameprofile == null) { ++ if (!c() && gameprofile == null && !org.apache.commons.lang3.StringUtils.isBlank(s)) { // Paper - Don't lookup a profile with a blank name + UUID uuid = EntityHuman.a(new GameProfile((UUID) null, s)); + + gameprofile = new GameProfile(uuid, s); diff --git a/patches/server-unmapped/0001/0109-Add-setting-for-proxy-online-mode-status.patch b/patches/server-unmapped/0001/0109-Add-setting-for-proxy-online-mode-status.patch new file mode 100644 index 0000000000..98ac7a6958 --- /dev/null +++ b/patches/server-unmapped/0001/0109-Add-setting-for-proxy-online-mode-status.patch @@ -0,0 +1,60 @@ +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 + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 1b8e5671c9dc8c15ce33d351c1bb20f28919b9a2..c52dc0346f93527965ef29a0ccdc4bf3debe302e 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -23,6 +23,7 @@ import org.bukkit.configuration.InvalidConfigurationException; + import org.bukkit.configuration.file.YamlConfiguration; + import co.aikar.timings.Timings; + import co.aikar.timings.TimingsManager; ++import org.spigotmc.SpigotConfig; + + public class PaperConfig { + +@@ -242,4 +243,13 @@ public class PaperConfig { + private static void saveEmptyScoreboardTeams() { + saveEmptyScoreboardTeams = getBoolean("settings.save-empty-scoreboard-teams", false); + } ++ ++ public static boolean bungeeOnlineMode = true; ++ private static void bungeeOnlineMode() { ++ bungeeOnlineMode = getBoolean("settings.bungee-online-mode", true); ++ } ++ ++ public static boolean isProxyOnlineMode() { ++ return Bukkit.getOnlineMode() || (SpigotConfig.bungee && bungeeOnlineMode); ++ } + } +diff --git a/src/main/java/net/minecraft/server/players/NameReferencingFileConverter.java b/src/main/java/net/minecraft/server/players/NameReferencingFileConverter.java +index 8a343a857dc4661ba256e39cf391dd2c7a1cc970..8c1f328ca1ba12ed63ec7bd7efad54ff633ba802 100644 +--- a/src/main/java/net/minecraft/server/players/NameReferencingFileConverter.java ++++ b/src/main/java/net/minecraft/server/players/NameReferencingFileConverter.java +@@ -66,7 +66,8 @@ public class NameReferencingFileConverter { + return new String[i]; + }); + +- if (minecraftserver.getOnlineMode() || org.spigotmc.SpigotConfig.bungee) { // Spigot: bungee = online mode, for now. ++ if (minecraftserver.getOnlineMode() ++ || (com.destroystokyo.paper.PaperConfig.isProxyOnlineMode())) { // Spigot: bungee = online mode, for now. // Paper - Handle via setting + minecraftserver.getGameProfileRepository().findProfilesByNames(astring, Agent.MINECRAFT, profilelookupcallback); + } 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 96abe76ab9d71561740499b4dfcfa94ded1c1b53..f1572f708911d61ae6dc0077475fee8d815e28db 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1507,7 +1507,8 @@ public final class CraftServer implements Server { + // Spigot Start + GameProfile profile = null; + // Only fetch an online UUID in online mode +- if ( getOnlineMode() || org.spigotmc.SpigotConfig.bungee ) ++ if ( getOnlineMode() ++ || com.destroystokyo.paper.PaperConfig.isProxyOnlineMode() ) // Paper - Handle via setting + { + profile = console.getUserCache().getProfile( name ); + } diff --git a/patches/server-unmapped/0001/0110-Optimise-BlockState-s-hashCode-equals.patch b/patches/server-unmapped/0001/0110-Optimise-BlockState-s-hashCode-equals.patch new file mode 100644 index 0000000000..909f6b5273 --- /dev/null +++ b/patches/server-unmapped/0001/0110-Optimise-BlockState-s-hashCode-equals.patch @@ -0,0 +1,84 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alfie Cleveland +Date: Fri, 19 Aug 2016 01:52:56 +0100 +Subject: [PATCH] Optimise BlockState's hashCode/equals + +These are singleton "single instance" objects. We can rely on +object identity checks safely. + +Use a simpler optimized hashcode + +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateBoolean.java b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateBoolean.java +index 0701c1a178852345b6bf01bce8b1d0559c535d45..f2f94950681b198ae7a4c31a044fd62e98e448ab 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateBoolean.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateBoolean.java +@@ -30,8 +30,7 @@ public class BlockStateBoolean extends IBlockState { + return obool.toString(); + } + +- @Override +- public boolean equals(Object object) { ++ public boolean equals_unused(Object object) { // Paper + if (this == object) { + return true; + } else if (object instanceof BlockStateBoolean && super.equals(object)) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateEnum.java b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateEnum.java +index de85894beae7ee7d276cf2af3daa77377ce131c3..3079cd13ea1465f4221fde4fec7df639f7c1eb49 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateEnum.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateEnum.java +@@ -50,8 +50,7 @@ public class BlockStateEnum & INamable> extends IBlockState + return ((INamable) t0).getName(); + } + +- @Override +- public boolean equals(Object object) { ++ public boolean equals_unused(Object object) { // Paper + if (this == object) { + return true; + } else if (object instanceof BlockStateEnum && super.equals(object)) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateInteger.java b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateInteger.java +index 518c2ebe4cdfe4704bbec2abe81522cbca38da55..190978c889222185b47065e9e5f96a82e59c7b4e 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateInteger.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/BlockStateInteger.java +@@ -38,8 +38,7 @@ public class BlockStateInteger extends IBlockState { + return this.a; + } + +- @Override +- public boolean equals(Object object) { ++ public boolean equals_unused(Object object) { // Paper + if (this == object) { + return true; + } else if (object instanceof BlockStateInteger && super.equals(object)) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IBlockState.java b/src/main/java/net/minecraft/world/level/block/state/properties/IBlockState.java +index e3969bad5be64bb41e2973751605d6820c16f021..759d6a4adaa511488ace5e2650eb685cbb6c4c16 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/IBlockState.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/IBlockState.java +@@ -60,23 +60,17 @@ public abstract class IBlockState> { + } + + public boolean equals(Object object) { +- if (this == object) { +- return true; +- } else if (!(object instanceof IBlockState)) { +- return false; +- } else { +- IBlockState iblockstate = (IBlockState) object; +- +- return this.a.equals(iblockstate.a) && this.b.equals(iblockstate.b); +- } ++ return this == object; // Paper - only one instance per configuration + } + ++ private static final java.util.concurrent.atomic.AtomicInteger hashId = new java.util.concurrent.atomic.AtomicInteger(1); // Paper - only one instance per configuration ++ private final int hashCode = 92821 * hashId.getAndIncrement(); // Paper - only one instance per configuration + public final int hashCode() { + if (this.c == null) { + this.c = this.b(); + } + +- return this.c; ++ return this.hashCode; // Paper - only one instance per configuration + } + + public int b() { diff --git a/patches/server-unmapped/0001/0111-Configurable-packet-in-spam-threshold.patch b/patches/server-unmapped/0001/0111-Configurable-packet-in-spam-threshold.patch new file mode 100644 index 0000000000..febebcbdef --- /dev/null +++ b/patches/server-unmapped/0001/0111-Configurable-packet-in-spam-threshold.patch @@ -0,0 +1,45 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index c52dc0346f93527965ef29a0ccdc4bf3debe302e..64d7c9058ee757a6d3cf3b648596092a810e105c 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -252,4 +252,13 @@ public class PaperConfig { + public static boolean isProxyOnlineMode() { + return Bukkit.getOnlineMode() || (SpigotConfig.bungee && bungeeOnlineMode); + } ++ ++ public static int packetInSpamThreshold = 300; ++ private static void packetInSpamThreshold() { ++ if (version < 11) { ++ int oldValue = getInt("settings.play-in-use-item-spam-threshold", 300); ++ set("settings.incoming-packet-spam-threshold", oldValue); ++ } ++ packetInSpamThreshold = getInt("settings.incoming-packet-spam-threshold", 300); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 7cb946ff73de5171debe34b37a784b6ed4e09150..702b06fac36f51bdb53d530e0c01bfe1dd67c527 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1464,13 +1464,14 @@ public class PlayerConnection implements PacketListenerPlayIn { + // Spigot start - limit place/interactions + private int limitedPackets; + private long lastLimitedPacket = -1; ++ private static final int THRESHOLD = com.destroystokyo.paper.PaperConfig.packetInSpamThreshold; // Paper - Configurable threshold + + private boolean checkLimit(long timestamp) { +- if (lastLimitedPacket != -1 && timestamp - lastLimitedPacket < 30 && limitedPackets++ >= 4) { ++ if (lastLimitedPacket != -1 && timestamp - lastLimitedPacket < THRESHOLD && limitedPackets++ >= 8) { // Paper - Use threshold, raise packet limit to 8 + return false; + } + +- if (lastLimitedPacket == -1 || timestamp - lastLimitedPacket >= 30) { ++ if (lastLimitedPacket == -1 || timestamp - lastLimitedPacket >= THRESHOLD) { // Paper + lastLimitedPacket = timestamp; + limitedPackets = 0; + return true; diff --git a/patches/server-unmapped/0001/0112-Configurable-flying-kick-messages.patch b/patches/server-unmapped/0001/0112-Configurable-flying-kick-messages.patch new file mode 100644 index 0000000000..62eca51bb7 --- /dev/null +++ b/patches/server-unmapped/0001/0112-Configurable-flying-kick-messages.patch @@ -0,0 +1,44 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 64d7c9058ee757a6d3cf3b648596092a810e105c..4e2f243faa209925dcb7c3ef89df3ed875c5ff78 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -261,4 +261,11 @@ public class PaperConfig { + } + packetInSpamThreshold = getInt("settings.incoming-packet-spam-threshold", 300); + } ++ ++ public static String flyingKickPlayerMessage = "Flying is not enabled on this server"; ++ public static String flyingKickVehicleMessage = "Flying is not enabled on this server"; ++ private static void flyingKickMessages() { ++ flyingKickPlayerMessage = getString("messages.kick.flying-player", flyingKickPlayerMessage); ++ flyingKickVehicleMessage = getString("messages.kick.flying-vehicle", flyingKickVehicleMessage); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 702b06fac36f51bdb53d530e0c01bfe1dd67c527..908d52f48b4bf2ddd638f2d718e1f6cb6148ce0a 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -307,7 +307,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + if (this.B && !this.player.isSleeping()) { + if (++this.C > 80) { + PlayerConnection.LOGGER.warn("{} was kicked for floating too long!", this.player.getDisplayName().getString()); +- this.disconnect(new ChatMessage("multiplayer.disconnect.flying")); ++ this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickPlayerMessage); // Paper - use configurable kick message + return; + } + } else { +@@ -326,7 +326,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + if (this.D && this.player.getRootVehicle().getRidingPassenger() == this.player) { + if (++this.E > 80) { + PlayerConnection.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getDisplayName().getString()); +- this.disconnect(new ChatMessage("multiplayer.disconnect.flying")); ++ this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickVehicleMessage); // Paper - use configurable kick message + return; + } + } else { diff --git a/patches/server-unmapped/0001/0113-Chunk-registration-fixes.patch b/patches/server-unmapped/0001/0113-Chunk-registration-fixes.patch new file mode 100644 index 0000000000..b6399aa64e --- /dev/null +++ b/patches/server-unmapped/0001/0113-Chunk-registration-fixes.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 21 Sep 2016 22:54:28 -0400 +Subject: [PATCH] Chunk registration fixes + +World checks and the Chunk Add logic are inconsistent on how Y > 256, < 0, is treated + +Keep them consistent + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index e008ef98d6902f5e1000da99870b12ae9d61bddb..6137a88e1dc8d19a4e35ad97500dabeddba008a8 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -847,7 +847,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + if (entity.cl()) { + this.getMethodProfiler().enter("chunkCheck"); + int i = MathHelper.floor(entity.locX() / 16.0D); +- int j = MathHelper.floor(entity.locY() / 16.0D); ++ int j = Math.min(15, Math.max(0, MathHelper.floor(entity.locY() / 16.0D))); // Paper - stay consistent with chunk add/remove behavior + int k = MathHelper.floor(entity.locZ() / 16.0D); + + if (!entity.inChunk || entity.chunkX != i || entity.chunkY != j || entity.chunkZ != k) { diff --git a/patches/server-unmapped/0001/0114-Remove-FishingHook-reference-on-Craft-Entity-removal.patch b/patches/server-unmapped/0001/0114-Remove-FishingHook-reference-on-Craft-Entity-removal.patch new file mode 100644 index 0000000000..ea0d507b64 --- /dev/null +++ b/patches/server-unmapped/0001/0114-Remove-FishingHook-reference-on-Craft-Entity-removal.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 16 Jun 2016 00:17:23 -0400 +Subject: [PATCH] Remove FishingHook reference on Craft Entity removal + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +index 4dd3deaabfdb383ded92920e1a313b61a1b9262b..4805bce05f2856289608f45df4fca322de161b31 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +@@ -119,4 +119,14 @@ public class CraftFishHook extends CraftProjectile implements FishHook { + public HookState getState() { + return HookState.values()[getHandle().hookState.ordinal()]; + } ++ ++ // Paper start ++ @Override ++ public void remove() { ++ super.remove(); ++ if (getHandle().getOwner() != null) { ++ getHandle().getOwner().hookedFish = null; ++ } ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0115-Auto-fix-bad-Y-levels-on-player-login.patch b/patches/server-unmapped/0001/0115-Auto-fix-bad-Y-levels-on-player-login.patch new file mode 100644 index 0000000000..c821ddeab5 --- /dev/null +++ b/patches/server-unmapped/0001/0115-Auto-fix-bad-Y-levels-on-player-login.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 21 Sep 2016 23:48:39 -0400 +Subject: [PATCH] Auto fix bad Y levels on player login + +Bring down to a saner Y level if super high, as this can cause the server to crash + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 120cad4e7330900fa11d278cf87a6ab4484469c3..db8b0d1327e94e81432343685233b2887c9a7d70 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -339,6 +339,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + @Override + public void loadData(NBTTagCompound nbttagcompound) { + super.loadData(nbttagcompound); ++ if (this.locY() > 300) this.setPositionRaw(locX(), 257, locZ()); // Paper - bring down to a saner Y level if out of world + if (nbttagcompound.hasKeyOfType("playerGameType", 99)) { + if (this.getMinecraftServer().getForceGamemode()) { + this.playerInteractManager.a(this.getMinecraftServer().getGamemode(), EnumGamemode.NOT_SET); diff --git a/patches/server-unmapped/0001/0116-Option-to-remove-corrupt-tile-entities.patch b/patches/server-unmapped/0001/0116-Option-to-remove-corrupt-tile-entities.patch new file mode 100644 index 0000000000..c9e047b2c4 --- /dev/null +++ b/patches/server-unmapped/0001/0116-Option-to-remove-corrupt-tile-entities.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 5 Oct 2016 16:27:36 -0500 +Subject: [PATCH] Option to remove corrupt tile entities + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 6eca3f300020006f02dd36253b522db442e3cc33..622affa0dc3cc1eadaed400511f2ca2cde3fca2a 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -300,4 +300,9 @@ public class PaperWorldConfig { + preventTntFromMovingInWater = getBoolean("prevent-tnt-from-moving-in-water", false); + log("Prevent TNT from moving in water: " + preventTntFromMovingInWater); + } ++ ++ public boolean removeCorruptTEs = false; ++ private void removeCorruptTEs() { ++ removeCorruptTEs = getBoolean("remove-corrupt-tile-entities", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index b6898cd6e6117fef65198db32b98a64c806811d4..7918dd4ad3e8cbb905b3929062a70fb7961b7d68 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -679,6 +679,12 @@ public class Chunk implements IChunkAccess { + "Chunk coordinates: " + (this.loc.x * 16) + "," + (this.loc.z * 16)); + e.printStackTrace(); + ServerInternalException.reportInternalException(e); ++ ++ if (this.world.paperConfig.removeCorruptTEs) { ++ this.removeTileEntity(tileentity.getPosition()); ++ this.markDirty(); ++ org.bukkit.Bukkit.getLogger().info("Removing corrupt tile entity"); ++ } + // Paper end + // CraftBukkit end + } diff --git a/patches/server-unmapped/0001/0117-Add-EntityZapEvent.patch b/patches/server-unmapped/0001/0117-Add-EntityZapEvent.patch new file mode 100644 index 0000000000..aea7acb873 --- /dev/null +++ b/patches/server-unmapped/0001/0117-Add-EntityZapEvent.patch @@ -0,0 +1,58 @@ +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/animal/EntityPig.java b/src/main/java/net/minecraft/world/entity/animal/EntityPig.java +index cc31c8f31a385f3a8bfe334e75c3553689397750..d6e1697f64e60f2a567288c604a1690159955f37 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityPig.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityPig.java +@@ -255,6 +255,11 @@ public class EntityPig extends EntityAnimal implements ISteerable, ISaddleable { + } + + entitypigzombie.setPersistent(); ++ // Paper start ++ if (CraftEventFactory.callEntityZapEvent(this, entitylightning, entitypigzombie).isCancelled()) { ++ return; ++ } ++ // Paper end + // CraftBukkit start + if (CraftEventFactory.callPigZapEvent(this, entitylightning, entitypigzombie).isCancelled()) { + return; +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +index 39071f82ae539c98499e9db37483ccecafc5f186..3604fffb9ba13a019e98e0a1a0ef7ba81c8dc329 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +@@ -787,6 +787,12 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + EntityVillager.LOGGER.info("Villager {} was struck by lightning {}.", this, entitylightning); + EntityWitch entitywitch = (EntityWitch) EntityTypes.WITCH.a((World) worldserver); + ++ // Paper start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, entitylightning, entitywitch).isCancelled()) { ++ return; ++ } ++ // Paper end ++ + entitywitch.setPositionRotation(this.locX(), this.locY(), this.locZ(), this.yaw, this.pitch); + entitywitch.prepare(worldserver, worldserver.getDamageScaler(entitywitch.getChunkCoordinates()), EnumMobSpawn.CONVERSION, (GroupDataEntity) null, (NBTTagCompound) 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 9175d66ec9ad2e8155d6dea64beadfa73b93cd48..ba599f6b80f9453584883d84846a976c3a6718df 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1077,6 +1077,14 @@ public class CraftEventFactory { + return event; + } + ++ // 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 HorseJumpEvent callHorseJumpEvent(Entity horse, float power) { + HorseJumpEvent event = new HorseJumpEvent((AbstractHorse) horse.getBukkitEntity(), power); + horse.getBukkitEntity().getServer().getPluginManager().callEvent(event); diff --git a/patches/server-unmapped/0001/0118-Filter-bad-data-from-ArmorStand-and-SpawnEgg-items.patch b/patches/server-unmapped/0001/0118-Filter-bad-data-from-ArmorStand-and-SpawnEgg-items.patch new file mode 100644 index 0000000000..4ed73d0d74 --- /dev/null +++ b/patches/server-unmapped/0001/0118-Filter-bad-data-from-ArmorStand-and-SpawnEgg-items.patch @@ -0,0 +1,49 @@ +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 data from ArmorStand and SpawnEgg items + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 622affa0dc3cc1eadaed400511f2ca2cde3fca2a..e83216be5a00d5b927d8c2fc364551bd3077c974 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -2,6 +2,7 @@ package com.destroystokyo.paper; + + import java.util.List; + ++import org.bukkit.Bukkit; + import org.bukkit.configuration.file.YamlConfiguration; + import org.spigotmc.SpigotWorldConfig; + +@@ -305,4 +306,12 @@ public class PaperWorldConfig { + private void removeCorruptTEs() { + removeCorruptTEs = getBoolean("remove-corrupt-tile-entities", false); + } ++ ++ public boolean filterNBTFromSpawnEgg = true; ++ private void fitlerNBTFromSpawnEgg() { ++ filterNBTFromSpawnEgg = getBoolean("filter-nbt-data-from-spawn-eggs-and-related", true); ++ if (!filterNBTFromSpawnEgg) { ++ Bukkit.getLogger().warning("Spawn Egg and Armor Stand NBT filtering disabled, this is a potential security risk"); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java b/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java +index 3f10e41b18e09186635fd6f7c653b04db7b39d8e..411e3915c0aa00249aacb6658ed04309665d2fb4 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java +@@ -272,6 +272,13 @@ public class EntityFallingBlock extends Entity { + @Override + protected void loadData(NBTTagCompound nbttagcompound) { + this.block = GameProfileSerializer.c(nbttagcompound.getCompound("BlockState")); ++ // Paper start - Block FallingBlocks with Command Blocks ++ // Check mappings on update - dc = "repeating_command_block" - dd = "chain_command_block" ++ final Block b = this.block.getBlock(); ++ if (this.world.paperConfig.filterNBTFromSpawnEgg && (b == Blocks.COMMAND_BLOCK || b == Blocks.REPEATING_COMMAND_BLOCK || b == Blocks.CHAIN_COMMAND_BLOCK)) { ++ this.block = Blocks.STONE.getBlockData(); ++ } ++ // Paper end + this.ticksLived = nbttagcompound.getInt("Time"); + if (nbttagcompound.hasKeyOfType("HurtEntities", 99)) { + this.hurtEntities = nbttagcompound.getBoolean("HurtEntities"); diff --git a/patches/server-unmapped/0001/0119-Cache-user-authenticator-threads.patch b/patches/server-unmapped/0001/0119-Cache-user-authenticator-threads.patch new file mode 100644 index 0000000000..240e844cbe --- /dev/null +++ b/patches/server-unmapped/0001/0119-Cache-user-authenticator-threads.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: vemacs +Date: Wed, 23 Nov 2016 08:31:45 -0500 +Subject: [PATCH] Cache user authenticator threads + + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index db8b0d1327e94e81432343685233b2887c9a7d70..4b9595f89c75b220fe70840a74e0aaa0276fa122 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -4,7 +4,9 @@ import com.google.common.collect.Lists; + import com.mojang.authlib.GameProfile; + import com.mojang.datafixers.util.Either; + import com.mojang.serialization.DataResult; ++import java.util.ArrayDeque; // Paper + import java.util.Collection; ++import java.util.Deque; // Paper + import java.util.Iterator; + import java.util.List; + import java.util.Optional; +@@ -171,7 +173,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public PlayerConnection playerConnection; + public final MinecraftServer server; + public final PlayerInteractManager playerInteractManager; +- public final List removeQueue = Lists.newLinkedList(); ++ public final Deque removeQueue = new ArrayDeque<>(); // Paper + private final AdvancementDataPlayer advancementDataPlayer; + private final ServerStatisticManager serverStatisticManager; + private float lastHealthScored = Float.MIN_VALUE; +@@ -549,13 +551,20 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + while (!this.removeQueue.isEmpty()) { + int i = Math.min(this.removeQueue.size(), Integer.MAX_VALUE); + int[] aint = new int[i]; +- Iterator iterator = this.removeQueue.iterator(); ++ //Iterator iterator = this.removeQueue.iterator(); // Paper + int j = 0; + +- while (iterator.hasNext() && j < i) { ++ // Paper start ++ /* while (iterator.hasNext() && j < i) { + aint[j++] = (Integer) iterator.next(); + iterator.remove(); ++ } */ ++ ++ Integer integer; ++ while (j < i && (integer = this.removeQueue.poll()) != null) { ++ aint[j++] = integer.intValue(); + } ++ // Paper end + + this.playerConnection.sendPacket(new PacketPlayOutEntityDestroy(aint)); + } +@@ -1552,7 +1561,14 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.lastHealthSent = -1.0F; + this.lastFoodSent = -1; + // this.recipeBook.a((RecipeBook) entityplayer.recipeBook); // CraftBukkit +- this.removeQueue.addAll(entityplayer.removeQueue); ++ // Paper start - Optimize remove queue - vanilla copies player objects, but CB doesn't. This method currently only ++ // Applies to the same player, so we need to not duplicate our removal queue. The rest of this method does "resetting" ++ // type logic so it does need to be called, maybe? This is silly. ++ // this.removeQueue.addAll(entityplayer.removeQueue); ++ if (this.removeQueue != entityplayer.removeQueue) { ++ this.removeQueue.addAll(entityplayer.removeQueue); ++ } ++ // Paper end + this.cd = entityplayer.cd; + this.ci = entityplayer.ci; + this.setShoulderEntityLeft(entityplayer.getShoulderEntityLeft()); diff --git a/patches/server-unmapped/0001/0120-Optimise-removeQueue.patch b/patches/server-unmapped/0001/0120-Optimise-removeQueue.patch new file mode 100644 index 0000000000..06625681a7 --- /dev/null +++ b/patches/server-unmapped/0001/0120-Optimise-removeQueue.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alfie Cleveland +Date: Fri, 25 Nov 2016 13:22:40 +0000 +Subject: [PATCH] Optimise removeQueue + + +diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java +index c4e3dd34c20c97ae1397cf51125f51a5b77d9437..9f87503c58f64bbfa829faa58600d7d9e64aecf1 100644 +--- a/src/main/java/net/minecraft/server/network/LoginListener.java ++++ b/src/main/java/net/minecraft/server/network/LoginListener.java +@@ -116,6 +116,12 @@ public class LoginListener implements PacketLoginInListener { + + } + ++ // Paper start - Cache authenticator threads ++ private static final AtomicInteger threadId = new AtomicInteger(0); ++ private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool( ++ r -> new Thread(r, "User Authenticator #" + threadId.incrementAndGet()) ++ ); ++ // Paper end + // Spigot start + public void initUUID() + { +@@ -194,8 +200,8 @@ public class LoginListener implements PacketLoginInListener { + this.networkManager.sendPacket(new PacketLoginOutEncryptionBegin("", this.server.getKeyPair().getPublic().getEncoded(), this.e)); + } else { + // Spigot start +- new Thread("User Authenticator #" + LoginListener.b.incrementAndGet()) { +- ++ // Paper start - Cache authenticator threads ++ authenticatorPool.execute(new Runnable() { + @Override + public void run() { + try { +@@ -206,7 +212,8 @@ public class LoginListener implements PacketLoginInListener { + server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + i.getName(), ex); + } + } +- }.start(); ++ }); ++ // Paper end + // Spigot end + } + +@@ -235,7 +242,8 @@ public class LoginListener implements PacketLoginInListener { + throw new IllegalStateException("Protocol error", cryptographyexception); + } + +- Thread thread = new Thread("User Authenticator #" + LoginListener.b.incrementAndGet()) { ++ // Paper start - Cache authenticator threads ++ authenticatorPool.execute(new Runnable() { + public void run() { + GameProfile gameprofile = LoginListener.this.i; + +@@ -280,10 +288,8 @@ public class LoginListener implements PacketLoginInListener { + + return LoginListener.this.server.W() && socketaddress instanceof InetSocketAddress ? ((InetSocketAddress) socketaddress).getAddress() : null; + } +- }; +- +- thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LoginListener.LOGGER)); +- thread.start(); ++ }); ++ // Paper end + } + + // Spigot start diff --git a/patches/server-unmapped/0001/0121-Allow-Reloading-of-Command-Aliases.patch b/patches/server-unmapped/0001/0121-Allow-Reloading-of-Command-Aliases.patch new file mode 100644 index 0000000000..e8889c3f30 --- /dev/null +++ b/patches/server-unmapped/0001/0121-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 f1572f708911d61ae6dc0077475fee8d815e28db..d3621b626799f470329e8f5097fc10016cd48560 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2273,5 +2273,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-unmapped/0001/0122-Add-source-to-PlayerExpChangeEvent.patch b/patches/server-unmapped/0001/0122-Add-source-to-PlayerExpChangeEvent.patch new file mode 100644 index 0000000000..d0fa01af67 --- /dev/null +++ b/patches/server-unmapped/0001/0122-Add-source-to-PlayerExpChangeEvent.patch @@ -0,0 +1,49 @@ +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/EntityExperienceOrb.java b/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java +index a52cd6d0318e0fee28fc5d252a4b596b92860320..a17812943b5402684c68ddeac5408dc939e42cf6 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java +@@ -203,7 +203,7 @@ public class EntityExperienceOrb extends Entity { + } + + if (this.value > 0) { +- entityhuman.giveExp(CraftEventFactory.callPlayerExpChangeEvent(entityhuman, this.value).getAmount()); // CraftBukkit - this.value -> event.getAmount() ++ entityhuman.giveExp(CraftEventFactory.callPlayerExpChangeEvent(entityhuman, this).getAmount()); // CraftBukkit - this.value -> event.getAmount() // Paper - supply experience orb object + } + + this.die(); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index ba599f6b80f9453584883d84846a976c3a6718df..cc8f366ab9e44fccb0633f34f6e04b6a6952e751 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -117,6 +117,7 @@ import org.bukkit.entity.ThrownPotion; + import org.bukkit.entity.Vehicle; + import org.bukkit.entity.Villager; + import org.bukkit.entity.Villager.Profession; ++import org.bukkit.entity.ExperienceOrb; // Paper + import org.bukkit.event.Cancellable; + import org.bukkit.event.Event; + import org.bukkit.event.Event.Result; +@@ -1036,6 +1037,17 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start - Add orb ++ public static PlayerExpChangeEvent callPlayerExpChangeEvent(EntityHuman entity, EntityExperienceOrb 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(World world, BlockPosition pos, IBlockData block) { + return handleBlockGrowEvent(world, pos, block, 3); + } diff --git a/patches/server-unmapped/0001/0123-Don-t-let-fishinghooks-use-portals.patch b/patches/server-unmapped/0001/0123-Don-t-let-fishinghooks-use-portals.patch new file mode 100644 index 0000000000..304934ac47 --- /dev/null +++ b/patches/server-unmapped/0001/0123-Don-t-let-fishinghooks-use-portals.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Fri, 16 Dec 2016 16:03:19 -0600 +Subject: [PATCH] Don't let fishinghooks use portals + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java +index bcc411107d531529dbce9d1d43896a3c70e63012..57e7b9c7f7f43666d442648120cda3b4b3e5bfb2 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java +@@ -240,6 +240,11 @@ public class EntityFishingHook extends IProjectile { + + this.setMot(this.getMot().a(0.92D)); + this.af(); ++ // Paper start - These shouldn't be going through portals ++ if (this.inPortal) { ++ this.die(); ++ } ++ // Paper end + } + } + diff --git a/patches/server-unmapped/0001/0124-Add-ProjectileCollideEvent.patch b/patches/server-unmapped/0001/0124-Add-ProjectileCollideEvent.patch new file mode 100644 index 0000000000..8006696261 --- /dev/null +++ b/patches/server-unmapped/0001/0124-Add-ProjectileCollideEvent.patch @@ -0,0 +1,109 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Techcable +Date: Fri, 16 Dec 2016 21:25:39 -0600 +Subject: [PATCH] Add ProjectileCollideEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java b/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java +index 9bd4a283a99f86c9a26f73e0bad0c3414d66ad55..5ecbe9135a71dd84e0722fa9c039c272a11d206f 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java +@@ -198,6 +198,17 @@ public abstract class EntityArrow extends IProjectile { + } + } + ++ // Paper start - Call ProjectileCollideEvent ++ // TODO: flag - noclip - call cancelled? ++ if (object instanceof MovingObjectPositionEntity) { ++ com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileCollideEvent(this, (MovingObjectPositionEntity)object); ++ if (event.isCancelled()) { ++ object = null; ++ movingobjectpositionentity = null; ++ } ++ } ++ // Paper end ++ + if (object != null && !flag) { + this.a((MovingObjectPosition) object); + this.impulse = true; +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityFireball.java b/src/main/java/net/minecraft/world/entity/projectile/EntityFireball.java +index ed76aec99f46a7923d139e347779c24f512ac131..ede7b4dbf2dce7bac83c5e17eecfdaf0e8a84fe7 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityFireball.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityFireball.java +@@ -13,6 +13,7 @@ import net.minecraft.world.entity.EntityLiving; + import net.minecraft.world.entity.EntityTypes; + import net.minecraft.world.level.World; + import net.minecraft.world.phys.MovingObjectPosition; ++import net.minecraft.world.phys.MovingObjectPositionEntity; + import net.minecraft.world.phys.Vec3D; + + import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit +@@ -72,7 +73,16 @@ public abstract class EntityFireball extends IProjectile { + + MovingObjectPosition movingobjectposition = ProjectileHelper.a((Entity) this, this::a); + +- if (movingobjectposition.getType() != MovingObjectPosition.EnumMovingObjectType.MISS) { ++ // Paper start - Call ProjectileCollideEvent ++ if (movingobjectposition instanceof MovingObjectPositionEntity) { ++ com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = CraftEventFactory.callProjectileCollideEvent(this, (MovingObjectPositionEntity)movingobjectposition); ++ if (event.isCancelled()) { ++ movingobjectposition = null; ++ } ++ } ++ // Paper end ++ ++ if (movingobjectposition != null && movingobjectposition.getType() != MovingObjectPosition.EnumMovingObjectType.MISS) { // Paper - add null check in case cancelled + this.a(movingobjectposition); + + // CraftBukkit start - Fire ProjectileHitEvent +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/EntityProjectile.java +index 829b4f28896bcb0eb6e48242bd00585eeaae62c2..3b379e83b79bd9b46dbdd4a48ac3842abc4dfbb8 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityProjectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityProjectile.java +@@ -14,6 +14,7 @@ import net.minecraft.world.level.block.entity.TileEntityEndGateway; + import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.phys.MovingObjectPosition; + import net.minecraft.world.phys.MovingObjectPositionBlock; ++import net.minecraft.world.phys.MovingObjectPositionEntity; + import net.minecraft.world.phys.Vec3D; + + public abstract class EntityProjectile extends IProjectile { +@@ -57,7 +58,17 @@ public abstract class EntityProjectile extends IProjectile { + } + + if (movingobjectposition.getType() != MovingObjectPosition.EnumMovingObjectType.MISS && !flag) { ++ // Paper start - Call ProjectileCollideEvent ++ if (movingobjectposition instanceof MovingObjectPositionEntity) { ++ com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileCollideEvent(this, (MovingObjectPositionEntity)movingobjectposition); ++ if (event.isCancelled()) { ++ movingobjectposition = null; ++ } ++ } ++ if (movingobjectposition != null) { ++ // Paper end + this.a(movingobjectposition); ++ } // Paper + } + + this.checkBlockCollisions(); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index cc8f366ab9e44fccb0633f34f6e04b6a6952e751..aa5c991fbcfef2d7ef9537321ae2e7d072184644 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1181,6 +1181,16 @@ public class CraftEventFactory { + return CraftItemStack.asNMSCopy(bitem); + } + ++ // Paper start ++ public static com.destroystokyo.paper.event.entity.ProjectileCollideEvent callProjectileCollideEvent(Entity entity, MovingObjectPositionEntity 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); diff --git a/patches/server-unmapped/0001/0125-Prevent-Pathfinding-out-of-World-Border.patch b/patches/server-unmapped/0001/0125-Prevent-Pathfinding-out-of-World-Border.patch new file mode 100644 index 0000000000..d30b0eae33 --- /dev/null +++ b/patches/server-unmapped/0001/0125-Prevent-Pathfinding-out-of-World-Border.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 19 Dec 2016 23:07:42 -0500 +Subject: [PATCH] Prevent Pathfinding out of World Border + +This prevents Entities from trying to run outside of the World Border + +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +index d71a6e5991629ce59c8529d7cc8064960e385236..d134333c736dc1ee1c722d680d7a9c22c1b265bd 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +@@ -148,7 +148,7 @@ public abstract class NavigationAbstract { + // Paper start - Pathfind event + boolean copiedSet = false; + for (BlockPosition possibleTarget : set) { +- if (!new com.destroystokyo.paper.event.entity.EntityPathfindEvent(getEntity().getBukkitEntity(), ++ if (!getEntity().getWorld().getWorldBorder().isInBounds(possibleTarget) || !new com.destroystokyo.paper.event.entity.EntityPathfindEvent(getEntity().getBukkitEntity(), // Paper - don't path out of world border + MCUtil.toLocation(getEntity().world, possibleTarget), target == null ? null : target.getBukkitEntity()).callEvent()) { + if (!copiedSet) { + copiedSet = true; diff --git a/patches/server-unmapped/0001/0126-Optimize-World.isLoaded-BlockPosition-Z.patch b/patches/server-unmapped/0001/0126-Optimize-World.isLoaded-BlockPosition-Z.patch new file mode 100644 index 0000000000..1b49bd0aa5 --- /dev/null +++ b/patches/server-unmapped/0001/0126-Optimize-World.isLoaded-BlockPosition-Z.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 2 Dec 2016 00:11:43 -0500 +Subject: [PATCH] Optimize World.isLoaded(BlockPosition)Z + +Reduce method invocations for World.isLoaded(BlockPosition)Z + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 46da7af8dd2202c299d8530c5ff570a0baae6bea..7456f3032a2213f592515c306b601eda542dad37 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -305,6 +305,10 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return chunk == null ? null : chunk.getFluid(blockposition); + } + ++ public final boolean isLoaded(BlockPosition blockposition) { ++ return getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4) != null; // Paper ++ } ++ + public final boolean isLoadedAndInBounds(BlockPosition blockposition) { // Paper - final for inline + return getWorldBorder().isInBounds(blockposition) && getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) != null; + } diff --git a/patches/server-unmapped/0001/0127-Bound-Treasure-Maps-to-World-Border.patch b/patches/server-unmapped/0001/0127-Bound-Treasure-Maps-to-World-Border.patch new file mode 100644 index 0000000000..212f2d89eb --- /dev/null +++ b/patches/server-unmapped/0001/0127-Bound-Treasure-Maps-to-World-Border.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 20 Dec 2016 15:15:11 -0500 +Subject: [PATCH] Bound Treasure Maps to World Border + +Make it so a Treasure Map does not target a structure outside of the +World Border, where players are not even able to reach. + +This also would help the case where a players close to the border, and one +that is outside happens to be closer, but unreachable, yet another reachable +one is in border that would of been missed. + +diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +index f16c76df5d7b184d57f4cc397f069eac9cc430cb..50e2085766caabec1125ca24a2117549efd1a354 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -36,6 +36,18 @@ public class WorldBorder { + return (double) (blockposition.getX() + 1) > this.e() && (double) blockposition.getX() < this.g() && (double) (blockposition.getZ() + 1) > this.f() && (double) blockposition.getZ() < this.h(); + } + ++ // Paper start ++ private final BlockPosition.MutableBlockPosition mutPos = new BlockPosition.MutableBlockPosition(); ++ public boolean isBlockInBounds(int chunkX, int chunkZ) { ++ this.mutPos.setValues(chunkX, 64, chunkZ); ++ return this.isInBounds(this.mutPos); ++ } ++ public boolean isChunkInBounds(int chunkX, int chunkZ) { ++ this.mutPos.setValues(((chunkX << 4) + 15), 64, (chunkZ << 4) + 15); ++ return this.isInBounds(this.mutPos); ++ } ++ // Paper end ++ + public boolean isInBounds(ChunkCoordIntPair chunkcoordintpair) { + return (double) chunkcoordintpair.f() > this.e() && (double) chunkcoordintpair.d() < this.g() && (double) chunkcoordintpair.g() > this.f() && (double) chunkcoordintpair.e() < this.h(); + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java +index 6724927be178cb9a358a9276d01894a63154b7b3..ea7e3e15fa778c573d24f956f72f60579ea0b1a1 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java +@@ -175,6 +175,7 @@ public abstract class StructureGenerator + int i2 = l + k * k1; + int j2 = i1 + k * l1; + ChunkCoordIntPair chunkcoordintpair = this.a(structuresettingsfeature, j, seededrandom, i2, j2); ++ if (!iworldreader.getWorldBorder().isChunkInBounds(chunkcoordintpair.x, chunkcoordintpair.z)) { continue; } // Paper + IChunkAccess ichunkaccess = iworldreader.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.STRUCTURE_STARTS); + StructureStart structurestart = structuremanager.a(SectionPosition.a(ichunkaccess.getPos(), 0), this, ichunkaccess); + diff --git a/patches/server-unmapped/0001/0128-Configurable-Cartographer-Treasure-Maps.patch b/patches/server-unmapped/0001/0128-Configurable-Cartographer-Treasure-Maps.patch new file mode 100644 index 0000000000..82ccdd75a9 --- /dev/null +++ b/patches/server-unmapped/0001/0128-Configurable-Cartographer-Treasure-Maps.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 20 Dec 2016 15:26:27 -0500 +Subject: [PATCH] Configurable Cartographer Treasure Maps + +Allow configuring for cartographers to return the same map location + +Also allow turning off treasure maps all together as they can eat up Map ID's +which are limited in quantity. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index e83216be5a00d5b927d8c2fc364551bd3077c974..2dc58b9f769ea43b737804456aafab47ecc143b8 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -314,4 +314,14 @@ public class PaperWorldConfig { + Bukkit.getLogger().warning("Spawn Egg and Armor Stand NBT filtering disabled, this is a potential security risk"); + } + } ++ ++ public boolean enableTreasureMaps = true; ++ public boolean treasureMapsAlreadyDiscovered = false; ++ private void treasureMapsAlreadyDiscovered() { ++ enableTreasureMaps = getBoolean("enable-treasure-maps", true); ++ treasureMapsAlreadyDiscovered = getBoolean("treasure-maps-return-already-discovered", false); ++ if (treasureMapsAlreadyDiscovered) { ++ log("Treasure Maps will return already discovered locations"); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java +index 764ff5d9ffb541a356a6bc8b321e619849dde747..0a34e319998a95a9654822e55a22eb964b2d626b 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java ++++ b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java +@@ -124,7 +124,8 @@ public class VillagerTrades { + return null; + } else { + WorldServer worldserver = (WorldServer) entity.world; +- BlockPosition blockposition = worldserver.a(this.b, entity.getChunkCoordinates(), 100, true); ++ if (!worldserver.paperConfig.enableTreasureMaps) return null; // Paper ++ BlockPosition blockposition = worldserver.a(this.b, entity.getChunkCoordinates(), 100, !worldserver.paperConfig.treasureMapsAlreadyDiscovered); // Paper + + if (blockposition != null) { + ItemStack itemstack = ItemWorldMap.createFilledMapView(worldserver, blockposition.getX(), blockposition.getZ(), (byte) 2, true, true); +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootItemFunctionExplorationMap.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootItemFunctionExplorationMap.java +index 38125a60bad4830db9de3580ab6d85fd122a0689..7bf16c5a3f2bb5525ce1ca0c0190c7671fc94797 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootItemFunctionExplorationMap.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootItemFunctionExplorationMap.java +@@ -64,7 +64,16 @@ public class LootItemFunctionExplorationMap extends LootItemFunctionConditional + + if (vec3d != null) { + WorldServer worldserver = loottableinfo.getWorld(); +- BlockPosition blockposition = worldserver.a(this.e, new BlockPosition(vec3d), this.h, this.i); ++ // Paper start ++ if (!worldserver.paperConfig.enableTreasureMaps) { ++ /* ++ * NOTE: I fear users will just get a plain map as their "treasure" ++ * This is preferable to disrespecting the config. ++ */ ++ return itemstack; ++ } ++ // Paper end ++ BlockPosition blockposition = worldserver.a(this.e, new BlockPosition(vec3d), this.h, !worldserver.paperConfig.treasureMapsAlreadyDiscovered && this.i); // Paper + + if (blockposition != null) { + ItemStack itemstack1 = ItemWorldMap.createFilledMapView(worldserver, blockposition.getX(), blockposition.getZ(), this.g, true, true); diff --git a/patches/server-unmapped/0001/0129-Optimize-ItemStack.isEmpty.patch b/patches/server-unmapped/0001/0129-Optimize-ItemStack.isEmpty.patch new file mode 100644 index 0000000000..61881f07d2 --- /dev/null +++ b/patches/server-unmapped/0001/0129-Optimize-ItemStack.isEmpty.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 21 Dec 2016 03:48:29 -0500 +Subject: [PATCH] Optimize ItemStack.isEmpty() + +Remove hashMap lookup every check, simplify code to remove ternary + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index d7c5065457d910f3e5481fda046d368d5f66f67b..58045d500a6fbb7eb568f48c7d8ce7730d357577 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -208,7 +208,7 @@ public final class ItemStack { + } + + public boolean isEmpty() { +- return this == ItemStack.b ? true : (this.getItem() != null && this.getItem() != Items.AIR ? this.count <= 0 : true); ++ return this == ItemStack.NULL_ITEM || this.item == null || this.item == Items.AIR || this.count <= 0; // Paper + } + + public ItemStack cloneAndSubtract(int i) { diff --git a/patches/server-unmapped/0001/0130-Add-API-methods-to-control-if-armour-stands-can-move.patch b/patches/server-unmapped/0001/0130-Add-API-methods-to-control-if-armour-stands-can-move.patch new file mode 100644 index 0000000000..6b9000b823 --- /dev/null +++ b/patches/server-unmapped/0001/0130-Add-API-methods-to-control-if-armour-stands-can-move.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Wed, 21 Dec 2016 11:47:25 -0600 +Subject: [PATCH] Add API methods to control if armour stands can move + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +index 0631cd531647239858b2a7298f58cc770720f69a..6ee5e1b0bb34ba490a130fbcbdb7a2706c5ecf86 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java ++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +@@ -38,6 +38,7 @@ import net.minecraft.world.entity.ai.control.ControllerLook; + import net.minecraft.world.entity.ai.control.ControllerMove; + import net.minecraft.world.entity.ai.control.EntityAIBodyControl; + import net.minecraft.world.entity.ai.goal.PathfinderGoal; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalFloat; + import net.minecraft.world.entity.ai.goal.PathfinderGoalSelector; + import net.minecraft.world.entity.ai.navigation.Navigation; + import net.minecraft.world.entity.ai.navigation.NavigationAbstract; +@@ -45,6 +46,8 @@ import net.minecraft.world.entity.ai.sensing.EntitySenses; + import net.minecraft.world.entity.decoration.EntityHanging; + import net.minecraft.world.entity.decoration.EntityLeash; + import net.minecraft.world.entity.item.EntityItem; ++import net.minecraft.world.entity.monster.EntityBlaze; ++import net.minecraft.world.entity.monster.EntityEnderman; + import net.minecraft.world.entity.monster.IMonster; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.entity.vehicle.EntityBoat; +diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +index 72e53968c5fb03301ddec7a0cf937ac2f8cf0901..2ef991aa7f739d3577fbbf4386064557e8f7c904 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +@@ -28,6 +28,7 @@ import net.minecraft.world.entity.EntitySize; + import net.minecraft.world.entity.EntityTypes; + import net.minecraft.world.entity.EnumItemSlot; + import net.minecraft.world.entity.EnumMainHand; ++import net.minecraft.world.entity.EnumMoveType; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.entity.projectile.EntityArrow; + import net.minecraft.world.entity.vehicle.EntityMinecartAbstract; +@@ -79,6 +80,7 @@ public class EntityArmorStand extends EntityLiving { + public Vector3f rightArmPose; + public Vector3f leftLegPose; + public Vector3f rightLegPose; ++ public boolean canMove = true; // Paper + + public EntityArmorStand(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -862,4 +864,13 @@ public class EntityArmorStand extends EntityLiving { + private EntitySize s(boolean flag) { + return flag ? EntityArmorStand.bp : (this.isBaby() ? EntityArmorStand.bq : this.getEntityType().l()); + } ++ ++ // Paper start ++ @Override ++ public void move(EnumMoveType moveType, Vec3D vec3d) { ++ if (this.canMove) { ++ super.move(moveType, vec3d); ++ } ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +index 86c8662b3864b264e26f8c63474fdd39bd6c873c..6f922e4cbb095439fcd76ee0d0c08bc4160b8107 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +@@ -229,4 +229,15 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + public boolean hasEquipmentLock(EquipmentSlot equipmentSlot, LockType lockType) { + return (getHandle().disabledSlots & (1 << CraftEquipmentSlot.getNMS(equipmentSlot).getSlotFlag() + lockType.ordinal() * 8)) != 0; + } ++ // Paper start ++ @Override ++ public boolean canMove() { ++ return getHandle().canMove; ++ } ++ ++ @Override ++ public void setCanMove(boolean move) { ++ getHandle().canMove = move; ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0131-Properly-fix-item-duplication-bug.patch b/patches/server-unmapped/0001/0131-Properly-fix-item-duplication-bug.patch new file mode 100644 index 0000000000..67b2c6d89d --- /dev/null +++ b/patches/server-unmapped/0001/0131-Properly-fix-item-duplication-bug.patch @@ -0,0 +1,35 @@ +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/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 4b9595f89c75b220fe70840a74e0aaa0276fa122..09385eabefeb7d59de1ce4138648badd123396f9 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -2060,8 +2060,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + + @Override +- public boolean isFrozen() { +- return super.isFrozen() || !getBukkitEntity().isOnline(); ++ protected boolean isFrozen() { ++ return super.isFrozen() || (this.playerConnection != null && this.playerConnection.isDisconnected()); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 908d52f48b4bf2ddd638f2d718e1f6cb6148ce0a..8a5a1ca08073d1a4df949a53fbf7e75339bf2761 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -2819,7 +2819,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + + public final boolean isDisconnected() { +- return !this.player.joining && !this.networkManager.isConnected(); ++ return (!this.player.joining && !this.networkManager.isConnected()) || this.processedDisconnect; // Paper + } + // CraftBukkit end + diff --git a/patches/server-unmapped/0001/0132-String-based-Action-Bar-API.patch b/patches/server-unmapped/0001/0132-String-based-Action-Bar-API.patch new file mode 100644 index 0000000000..20f702475a --- /dev/null +++ b/patches/server-unmapped/0001/0132-String-based-Action-Bar-API.patch @@ -0,0 +1,48 @@ +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/SystemUtils.java b/src/main/java/net/minecraft/SystemUtils.java +index c8bb06a31242089ad950713bd5f94abbfe12adc8..68ce7605bd63ea280b96db8230463d2afb0a6cb1 100644 +--- a/src/main/java/net/minecraft/SystemUtils.java ++++ b/src/main/java/net/minecraft/SystemUtils.java +@@ -58,7 +58,7 @@ public class SystemUtils { + private static final ExecutorService e = a("Main"); + private static final ExecutorService f = n(); + public static LongSupplier a = System::nanoTime; +- public static final UUID b = new UUID(0L, 0L); ++ public static final UUID b = new UUID(0L, 0L); public static final UUID getNullUUID() {return b;} // Paper OBFHELPER + private static final Logger LOGGER = LogManager.getLogger(); + + public static Collector, ?, Map> a() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 6d7f1dee9ae2fb0b9620d85969de86eee09020cc..c3058d6fca2fd58aea5001e4310592aa8bd20640 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -244,6 +244,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + // Paper start ++ @Override ++ public void sendActionBar(BaseComponent[] message) { ++ if (getHandle().playerConnection == null) return; ++ getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.ACTIONBAR, message, -1, -1, -1)); ++ } ++ ++ @Override ++ public void sendActionBar(String message) { ++ if (getHandle().playerConnection == null || message == null || message.isEmpty()) return; ++ getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.ACTIONBAR, CraftChatMessage.fromStringOrNull(message))); ++ } ++ ++ @Override ++ public void sendActionBar(char alternateChar, String message) { ++ if (message == null || message.isEmpty()) return; ++ sendActionBar(org.bukkit.ChatColor.translateAlternateColorCodes(alternateChar, message)); ++ } ++ + @Override + public void setPlayerListHeaderFooter(BaseComponent[] header, BaseComponent[] footer) { + if (header != null) { diff --git a/patches/server-unmapped/0001/0133-Firework-API-s.patch b/patches/server-unmapped/0001/0133-Firework-API-s.patch new file mode 100644 index 0000000000..e42242de5c --- /dev/null +++ b/patches/server-unmapped/0001/0133-Firework-API-s.patch @@ -0,0 +1,124 @@ +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 + + +diff --git a/src/main/java/net/minecraft/nbt/NBTTagCompound.java b/src/main/java/net/minecraft/nbt/NBTTagCompound.java +index bf4826e90976fed2ae95e84cadc7f29433af1ddf..d5508deff819309034554abc7b36aac40fa33503 100644 +--- a/src/main/java/net/minecraft/nbt/NBTTagCompound.java ++++ b/src/main/java/net/minecraft/nbt/NBTTagCompound.java +@@ -153,6 +153,7 @@ public class NBTTagCompound implements NBTBase { + return GameProfileSerializer.a(this.get(s)); + } + ++ public final boolean hasUUID(String s) { return this.b(s); } // Paper - OBFHELPER + public boolean b(String s) { + NBTBase nbtbase = this.get(s); + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityFireworks.java b/src/main/java/net/minecraft/world/entity/projectile/EntityFireworks.java +index 2df84b56ef35a18648e74a134ac7ab97c518e481..9cc59439ae2c4e758c44b2a92b78bc328efdfa1b 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityFireworks.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityFireworks.java +@@ -38,7 +38,8 @@ public class EntityFireworks extends IProjectile { + public static final DataWatcherObject SHOT_AT_ANGLE = DataWatcher.a(EntityFireworks.class, DataWatcherRegistry.i); + private int ticksFlown; + public int expectedLifespan; +- private EntityLiving ridingEntity; ++ public EntityLiving ridingEntity; // Paper - public ++ public java.util.UUID spawningEntity; // Paper + + public EntityFireworks(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -285,6 +286,11 @@ public class EntityFireworks extends IProjectile { + } + + nbttagcompound.setBoolean("ShotAtAngle", (Boolean) this.datawatcher.get(EntityFireworks.SHOT_AT_ANGLE)); ++ // Paper start ++ if (this.spawningEntity != null) { ++ nbttagcompound.setUUID("SpawningEntity", this.spawningEntity); ++ } ++ // Paper end + } + + @Override +@@ -301,7 +307,11 @@ public class EntityFireworks extends IProjectile { + if (nbttagcompound.hasKey("ShotAtAngle")) { + this.datawatcher.set(EntityFireworks.SHOT_AT_ANGLE, nbttagcompound.getBoolean("ShotAtAngle")); + } +- ++ // Paper start ++ if (nbttagcompound.hasUUID("SpawningEntity")) { ++ this.spawningEntity = nbttagcompound.getUUID("SpawningEntity"); ++ } ++ // Paper end + } + + @Override +diff --git a/src/main/java/net/minecraft/world/item/ItemCrossbow.java b/src/main/java/net/minecraft/world/item/ItemCrossbow.java +index d52f3cb5d76bde1cf29c654dade6d8379b44c2e5..ec6c0836f02e7ac5b72fd224a3022a844dce55cb 100644 +--- a/src/main/java/net/minecraft/world/item/ItemCrossbow.java ++++ b/src/main/java/net/minecraft/world/item/ItemCrossbow.java +@@ -205,6 +205,7 @@ public class ItemCrossbow extends ItemProjectileWeapon implements ItemVanishable + + if (flag1) { + object = new EntityFireworks(world, itemstack1, entityliving, entityliving.locX(), entityliving.getHeadY() - 0.15000000596046448D, entityliving.locZ(), true); ++ ((EntityFireworks) object).spawningEntity = entityliving.getUniqueID(); // Paper + } else { + object = a(world, entityliving, itemstack, itemstack1); + if (flag || f3 != 0.0F) { +diff --git a/src/main/java/net/minecraft/world/item/ItemFireworks.java b/src/main/java/net/minecraft/world/item/ItemFireworks.java +index 9153945c2e245b9a2a098bdf58b0dcab052084ff..a2950faa48021782f10db0673d12d178443f7ccc 100644 +--- a/src/main/java/net/minecraft/world/item/ItemFireworks.java ++++ b/src/main/java/net/minecraft/world/item/ItemFireworks.java +@@ -27,6 +27,7 @@ public class ItemFireworks extends Item { + Vec3D vec3d = itemactioncontext.getPos(); + EnumDirection enumdirection = itemactioncontext.getClickedFace(); + EntityFireworks entityfireworks = new EntityFireworks(world, itemactioncontext.getEntity(), vec3d.x + (double) enumdirection.getAdjacentX() * 0.15D, vec3d.y + (double) enumdirection.getAdjacentY() * 0.15D, vec3d.z + (double) enumdirection.getAdjacentZ() * 0.15D, itemstack); ++ entityfireworks.spawningEntity = itemactioncontext.getEntity().getUniqueID(); // Paper + + world.addEntity(entityfireworks); + itemstack.subtract(1); +@@ -41,7 +42,11 @@ public class ItemFireworks extends Item { + ItemStack itemstack = entityhuman.b(enumhand); + + if (!world.isClientSide) { +- world.addEntity(new EntityFireworks(world, itemstack, entityhuman)); ++ // Paper start ++ final EntityFireworks entityfireworks = new EntityFireworks(world, itemstack, entityhuman); ++ entityfireworks.spawningEntity = entityhuman.getUniqueID(); ++ world.addEntity(entityfireworks); ++ // Paper end + if (!entityhuman.abilities.canInstantlyBuild) { + itemstack.subtract(1); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +index 33162fd419ab9a7b650ca9d4270a0c03f06f19f6..73c2da316e41329114fcb3d30cb009d9cc7de7b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +@@ -1,6 +1,7 @@ + package org.bukkit.craftbukkit.entity; + + import java.util.Random; ++import net.minecraft.world.entity.EntityLiving; + import net.minecraft.world.entity.projectile.EntityFireworks; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; +@@ -78,4 +79,17 @@ public class CraftFirework extends CraftProjectile implements Firework { + public void setShotAtAngle(boolean shotAtAngle) { + getHandle().getDataWatcher().set(EntityFireworks.SHOT_AT_ANGLE, shotAtAngle); + } ++ ++ // Paper start ++ @Override ++ public java.util.UUID getSpawningEntity() { ++ return getHandle().spawningEntity; ++ } ++ ++ @Override ++ public org.bukkit.entity.LivingEntity getBoostedEntity() { ++ EntityLiving boostedEntity = getHandle().ridingEntity; ++ return boostedEntity != null ? (org.bukkit.entity.LivingEntity) boostedEntity.getBukkitEntity() : null; ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0134-PlayerTeleportEndGatewayEvent.patch b/patches/server-unmapped/0001/0134-PlayerTeleportEndGatewayEvent.patch new file mode 100644 index 0000000000..6e0a520b62 --- /dev/null +++ b/patches/server-unmapped/0001/0134-PlayerTeleportEndGatewayEvent.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 31 Dec 2016 21:44:50 -0500 +Subject: [PATCH] PlayerTeleportEndGatewayEvent + +Allows you to access the Gateway being used in a teleport event + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java +index 0c5d9600eadc0a550cc2d5e7b4ee665c030faa89..2808cd0b100bd65a730aba315ab47a59a4621b30 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java +@@ -10,6 +10,7 @@ import net.minecraft.data.worldgen.BiomeDecoratorGroups; + import net.minecraft.nbt.GameProfileSerializer; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.protocol.game.PacketPlayOutTileEntityData; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.WorldServer; + import net.minecraft.util.MathHelper; +@@ -180,7 +181,7 @@ public class TileEntityEndGateway extends TileEntityEnderPortal implements ITick + location.setPitch(player.getLocation().getPitch()); + location.setYaw(player.getLocation().getYaw()); + +- PlayerTeleportEvent teleEvent = new PlayerTeleportEvent(player, player.getLocation(), location, PlayerTeleportEvent.TeleportCause.END_GATEWAY); ++ PlayerTeleportEvent teleEvent = new com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent(player, player.getLocation(), location, new org.bukkit.craftbukkit.block.CraftEndGateway(MCUtil.toLocation(world, this.getPosition()).getBlock())); // Paper + Bukkit.getPluginManager().callEvent(teleEvent); + if (teleEvent.isCancelled()) { + return; diff --git a/patches/server-unmapped/0001/0135-Provide-E-TE-Chunk-count-stat-methods.patch b/patches/server-unmapped/0001/0135-Provide-E-TE-Chunk-count-stat-methods.patch new file mode 100644 index 0000000000..1017dab7e2 --- /dev/null +++ b/patches/server-unmapped/0001/0135-Provide-E-TE-Chunk-count-stat-methods.patch @@ -0,0 +1,61 @@ +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/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 5a851074f5041a6e4d4f7297cf02e8a17b62905f..ccd110c4bfaa263e04154dcc2a5bdbff1f3a7ec2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -282,6 +282,48 @@ public class CraftWorld implements World { + private int waterAmbientSpawn = -1; + private int ambientSpawn = -1; + ++ // Paper start - Provide fast information methods ++ public int getEntityCount() { ++ int ret = 0; ++ for (net.minecraft.world.entity.Entity entity : world.entitiesById.values()) { ++ if (entity.isChunkLoaded()) { ++ ++ret; ++ } ++ } ++ return ret; ++ } ++ public int getTileEntityCount() { ++ // We don't use the full world tile entity list, so we must iterate chunks ++ Long2ObjectLinkedOpenHashMap chunks = world.getChunkProvider().playerChunkMap.visibleChunks; ++ int size = 0; ++ for (PlayerChunk playerchunk : chunks.values()) { ++ net.minecraft.world.level.chunk.Chunk chunk = playerchunk.getChunk(); ++ if (chunk == null) { ++ continue; ++ } ++ size += chunk.tileEntities.size(); ++ } ++ return size; ++ } ++ public int getTickableTileEntityCount() { ++ return world.tileEntityListTick.size(); ++ } ++ public int getChunkCount() { ++ int ret = 0; ++ ++ for (PlayerChunk chunkHolder : world.getChunkProvider().playerChunkMap.visibleChunks.values()) { ++ if (chunkHolder.getChunk() != null) { ++ ++ret; ++ } ++ } ++ ++ return ret; ++ } ++ public int getPlayerCount() { ++ return world.players.size(); ++ } ++ // Paper end ++ + private static final Random rand = new Random(); + + public CraftWorld(WorldServer world, ChunkGenerator gen, Environment env) { diff --git a/patches/server-unmapped/0001/0136-Enforce-Sync-Player-Saves.patch b/patches/server-unmapped/0001/0136-Enforce-Sync-Player-Saves.patch new file mode 100644 index 0000000000..0329f26b85 --- /dev/null +++ b/patches/server-unmapped/0001/0136-Enforce-Sync-Player-Saves.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 7 Jan 2017 15:41:58 -0500 +Subject: [PATCH] Enforce Sync Player Saves + +Saving players async is extremely dangerous. This will force it to main +the same way we handle async chunk loads. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index f9e9e51b0b0dcbf2a8424c7c14bd2cbb0d899e82..eb5c22d8af3a3cfadd581d641010942caa6bed54 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1047,11 +1047,13 @@ public abstract class PlayerList { + } + + public void savePlayers() { ++ MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main + MinecraftTimings.savePlayers.startTiming(); // Paper + for (int i = 0; i < this.players.size(); ++i) { + this.savePlayerFile((EntityPlayer) this.players.get(i)); + } + MinecraftTimings.savePlayers.stopTiming(); // Paper ++ return null; }); // Paper - ensure main + } + + public WhiteList getWhitelist() { diff --git a/patches/server-unmapped/0001/0137-Don-t-allow-entities-to-ride-themselves-572.patch b/patches/server-unmapped/0001/0137-Don-t-allow-entities-to-ride-themselves-572.patch new file mode 100644 index 0000000000..c081affdfe --- /dev/null +++ b/patches/server-unmapped/0001/0137-Don-t-allow-entities-to-ride-themselves-572.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alfie Cleveland +Date: Sun, 8 Jan 2017 04:31:36 +0000 +Subject: [PATCH] Don't allow entities to ride themselves - #572 + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 88ffc594a2ee7f8718337883609ad4c082f85f50..392bb2195e47a665c7be6b1045aa0ae7a509714a 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2041,6 +2041,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + protected boolean addPassenger(Entity entity) { // CraftBukkit ++ if (entity == this) throw new IllegalArgumentException("Entities cannot become a passenger of themselves"); // Paper - issue 572 + if (entity.getVehicle() != this) { + throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)"); + } else { diff --git a/patches/server-unmapped/0001/0138-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch b/patches/server-unmapped/0001/0138-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch new file mode 100644 index 0000000000..407499f483 --- /dev/null +++ b/patches/server-unmapped/0001/0138-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch @@ -0,0 +1,315 @@ +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/PlayerInteractManager.java b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +index e9730597f2bd55a021f212d5eb5a76a26f320fb0..51157a9223f3da22d1110cfa211a502de59fb8a1 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerInteractManager.java ++++ b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +@@ -408,7 +408,7 @@ public class PlayerInteractManager { + + // Drop event experience + if (flag && event != null) { +- iblockdata.getBlock().dropExperience(this.world, blockposition, event.getExpToDrop()); ++ iblockdata.getBlock().dropExperience(this.world, blockposition, event.getExpToDrop(), this.player); // Paper + } + + return true; +diff --git a/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java b/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java +index a17812943b5402684c68ddeac5408dc939e42cf6..f4da22b33c704e675510b4b1a3aa7c180088be29 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java +@@ -31,9 +31,59 @@ public class EntityExperienceOrb extends Entity { + public int value; + private EntityHuman targetPlayer; + private int targetTime; ++ // Paper start ++ public java.util.UUID sourceEntityId; ++ public java.util.UUID triggerEntityId; ++ public org.bukkit.entity.ExperienceOrb.SpawnReason spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; ++ ++ private void loadPaperNBT(NBTTagCompound nbttagcompound) { ++ if (!nbttagcompound.hasKeyOfType("Paper.ExpData", 10)) { // 10 = compound ++ return; ++ } ++ NBTTagCompound comp = nbttagcompound.getCompound("Paper.ExpData"); ++ if (comp.hasUUID("source")) { ++ this.sourceEntityId = comp.getUUID("source"); ++ } ++ if (comp.hasUUID("trigger")) { ++ this.triggerEntityId = comp.getUUID("trigger"); ++ } ++ if (comp.hasKey("reason")) { ++ String reason = comp.getString("reason"); ++ try { ++ this.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.valueOf(reason); ++ } catch (Exception e) { ++ this.world.getServer().getLogger().warning("Invalid spawnReason set for experience orb: " + e.getMessage() + " - " + reason); ++ } ++ } ++ } ++ private void savePaperNBT(NBTTagCompound nbttagcompound) { ++ NBTTagCompound comp = new NBTTagCompound(); ++ if (this.sourceEntityId != null) { ++ comp.setUUID("source", this.sourceEntityId); ++ } ++ if (this.triggerEntityId != null) { ++ comp.setUUID("trigger", triggerEntityId); ++ } ++ if (this.spawnReason != null && this.spawnReason != org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN) { ++ comp.setString("reason", this.spawnReason.name()); ++ } ++ nbttagcompound.set("Paper.ExpData", comp); ++ } + + public EntityExperienceOrb(World world, double d0, double d1, double d2, int i) { ++ this(world, d0, d1, d2, i, null, null); ++ } ++ ++ public EntityExperienceOrb(World world, double d0, double d1, double d2, int i, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId) { ++ this(world, d0, d1, d2, i, reason, triggerId, null); ++ } ++ ++ public EntityExperienceOrb(World world, double d0, double d1, double d2, int i, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId, Entity sourceId) { + this(EntityTypes.EXPERIENCE_ORB, world); ++ this.sourceEntityId = sourceId != null ? sourceId.getUniqueID() : null; ++ this.triggerEntityId = triggerId != null ? triggerId.getUniqueID() : null; ++ this.spawnReason = reason != null ? reason : org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; ++ // Paper end + this.setPosition(d0, d1, d2); + this.yaw = (float) (this.random.nextDouble() * 360.0D); + this.setMot((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); +@@ -168,6 +218,7 @@ public class EntityExperienceOrb extends Entity { + nbttagcompound.setShort("Health", (short) this.e); + nbttagcompound.setShort("Age", (short) this.c); + nbttagcompound.setShort("Value", (short) this.value); ++ this.savePaperNBT(nbttagcompound); // Paper + } + + @Override +@@ -175,6 +226,7 @@ public class EntityExperienceOrb extends Entity { + this.e = nbttagcompound.getShort("Health"); + this.c = nbttagcompound.getShort("Age"); + this.value = nbttagcompound.getShort("Value"); ++ this.loadPaperNBT(nbttagcompound); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index dc715a0275b148c3c66610d6fb873626180c82d5..b66b340fdef9423ad8dce290065e028a0c135ea8 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -1594,7 +1594,8 @@ public abstract class EntityLiving extends Entity { + int j = EntityExperienceOrb.getOrbValue(i); + + i -= j; +- this.world.addEntity(new EntityExperienceOrb(this.world, this.locX(), this.locY(), this.locZ(), j)); ++ EntityLiving attacker = killer != null ? killer : lastDamager; // Paper ++ this.world.addEntity(new EntityExperienceOrb(this.world, this.locX(), this.locY(), this.locZ(), j, this instanceof EntityPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, attacker, this)); // Paper + } + this.expToDrop = 0; + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityAnimal.java b/src/main/java/net/minecraft/world/entity/animal/EntityAnimal.java +index 8d0c7469999bb6d75debf427ff4d7fa5d2d5c505..28dd42921961c6a47f2d85a5f93b8298f2c228d3 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityAnimal.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityAnimal.java +@@ -262,7 +262,7 @@ public abstract class EntityAnimal extends EntityAgeable { + if (worldserver.getGameRules().getBoolean(GameRules.DO_MOB_LOOT)) { + // CraftBukkit start - use event experience + if (experience > 0) { +- worldserver.addEntity(new EntityExperienceOrb(worldserver, this.locX(), this.locY(), this.locZ(), experience)); ++ worldserver.addEntity(new EntityExperienceOrb(worldserver, this.locX(), this.locY(), this.locZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityageable)); // Paper + } + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityFox.java b/src/main/java/net/minecraft/world/entity/animal/EntityFox.java +index 7941a083353bb1d9ba81c41d7a566b72bdc955d9..459b7727e946679989477f4a7e99c5ca47ac0b30 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityFox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityFox.java +@@ -1306,7 +1306,7 @@ public class EntityFox extends EntityAnimal { + if (this.b.getGameRules().getBoolean(GameRules.DO_MOB_LOOT)) { + // CraftBukkit start - use event experience + if (experience > 0) { +- this.b.addEntity(new EntityExperienceOrb(this.b, this.animal.locX(), this.animal.locY(), this.animal.locZ(), experience)); ++ this.b.addEntity(new EntityExperienceOrb(this.b, this.animal.locX(), this.animal.locY(), this.animal.locZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityfox)); // Paper + } + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java b/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java +index 5d08b83e2832cad2c8726ae817e003d970bc52a0..bf224c97854daa379c61affff6a0ac9524c2c35d 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java +@@ -561,7 +561,7 @@ public class EntityTurtle extends EntityAnimal { + Random random = this.animal.getRandom(); + + if (this.b.getGameRules().getBoolean(GameRules.DO_MOB_LOOT)) { +- this.b.addEntity(new EntityExperienceOrb(this.b, this.animal.locX(), this.animal.locY(), this.animal.locZ(), random.nextInt(7) + 1)); ++ this.b.addEntity(new EntityExperienceOrb(this.b, this.animal.locX(), this.animal.locY(), this.animal.locZ(), random.nextInt(7) + 1, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper; + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java +index 74802de01dba30e38e09f6fc1f61e7bb64cf5f09..97ef4c65c8cc569a99d9697f56bd44d32b151328 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java +@@ -661,7 +661,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + int j = EntityExperienceOrb.getOrbValue(i); + + i -= j; +- this.world.addEntity(new EntityExperienceOrb(this.world, this.locX(), this.locY(), this.locZ(), j)); ++ this.world.addEntity(new EntityExperienceOrb(this.world, this.locX(), this.locY(), this.locZ(), j, org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.killer, this)); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +index 3604fffb9ba13a019e98e0a1a0ef7ba81c8dc329..adce6f17a5dd33004f8a67cd55d195de029e0263 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +@@ -600,7 +600,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + } + + if (merchantrecipe.isRewardExp()) { +- this.world.addEntity(new EntityExperienceOrb(this.world, this.locX(), this.locY() + 0.5D, this.locZ(), i)); ++ this.world.addEntity(new EntityExperienceOrb(this.world, this.locX(), this.locY() + 0.5D, this.locZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTrader(), this)); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerTrader.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerTrader.java +index 46da22aeef6132a96e413301935c4fef7a96e0ee..4f81a97b1451fec0bb5fd1479acad97846c40c7c 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerTrader.java +@@ -189,7 +189,7 @@ public class EntityVillagerTrader extends EntityVillagerAbstract { + if (merchantrecipe.isRewardExp()) { + int i = 3 + this.random.nextInt(4); + +- this.world.addEntity(new EntityExperienceOrb(this.world, this.locX(), this.locY() + 0.5D, this.locZ(), i)); ++ this.world.addEntity(new EntityExperienceOrb(this.world, this.locX(), this.locY() + 0.5D, this.locZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTrader(), this)); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java +index 57e7b9c7f7f43666d442648120cda3b4b3e5bfb2..d40b056b2ff14033113bd7108a3295f8783b8bdf 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java +@@ -503,7 +503,7 @@ public class EntityFishingHook extends IProjectile { + this.world.addEntity(entityitem); + // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() + if (playerFishEvent.getExpToDrop() > 0) { +- entityhuman.world.addEntity(new EntityExperienceOrb(entityhuman.world, entityhuman.locX(), entityhuman.locY() + 0.5D, entityhuman.locZ() + 0.5D, playerFishEvent.getExpToDrop())); ++ entityhuman.world.addEntity(new EntityExperienceOrb(entityhuman.world, entityhuman.locX(), entityhuman.locY() + 0.5D, entityhuman.locZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getOwner(), this)); // Paper + } + // CraftBukkit end + if (itemstack1.getItem().a((Tag) TagsItem.FISHES)) { +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityThrownExpBottle.java b/src/main/java/net/minecraft/world/entity/projectile/EntityThrownExpBottle.java +index 87fe16c81b57ba07399f5566ab8bd77d71db36a1..e07353a6b34196e3d275ba482fbef7e4d209c31d 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityThrownExpBottle.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityThrownExpBottle.java +@@ -54,7 +54,7 @@ public class EntityThrownExpBottle extends EntityProjectileThrowable { + int j = EntityExperienceOrb.getOrbValue(i); + + i -= j; +- this.world.addEntity(new EntityExperienceOrb(this.world, this.locX(), this.locY(), this.locZ(), j)); ++ this.world.addEntity(new EntityExperienceOrb(this.world, this.locX(), this.locY(), this.locZ(), j, org.bukkit.entity.ExperienceOrb.SpawnReason.EXP_BOTTLE, getShooter(), this)); // Paper + } + + this.die(); +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java b/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java +index cba1de50f3035ae1b9366f474745d50a1f8fc014..fad7355a549aef811bca43be198af3d1c0a53980 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java +@@ -94,7 +94,7 @@ public class ContainerGrindstone extends Container { + int k = EntityExperienceOrb.getOrbValue(j); + + j -= k; +- world.addEntity(new EntityExperienceOrb(world, (double) blockposition.getX(), (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, k)); ++ world.addEntity(new EntityExperienceOrb(world, (double) blockposition.getX(), (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, k, org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, entityhuman)); // Paper + } + + world.triggerEffect(1042, blockposition, 0); +diff --git a/src/main/java/net/minecraft/world/inventory/SlotFurnaceResult.java b/src/main/java/net/minecraft/world/inventory/SlotFurnaceResult.java +index 48324295913dd9a9bd31332b5b811a729b621dfb..a114e576e5d80c25cc7f2b17f0dc3ad706a1b877 100644 +--- a/src/main/java/net/minecraft/world/inventory/SlotFurnaceResult.java ++++ b/src/main/java/net/minecraft/world/inventory/SlotFurnaceResult.java +@@ -7,7 +7,7 @@ import net.minecraft.world.level.block.entity.TileEntityFurnace; + + public class SlotFurnaceResult extends Slot { + +- private final EntityHuman a; ++ private final EntityHuman a; public final EntityHuman getPlayer() { return this.a; } // Paper OBFHELPER + private int b; + + public SlotFurnaceResult(EntityHuman entityhuman, IInventory iinventory, int i, int j, int k) { +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 d285c4e3d9f938973bf7fb904680044b414e6236..615a4418fd276cd3e0b3686d962ebaf13ef5d4be 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -267,13 +267,13 @@ public class Block extends BlockBase implements IMaterial { + } + } + +- public void dropExperience(WorldServer worldserver, BlockPosition blockposition, int i) { ++ public void dropExperience(WorldServer worldserver, BlockPosition blockposition, int i, net.minecraft.server.level.EntityPlayer player) { // Paper + if (worldserver.getGameRules().getBoolean(GameRules.DO_TILE_DROPS)) { + while (i > 0) { + int j = EntityExperienceOrb.getOrbValue(i); + + i -= j; +- worldserver.addEntity(new EntityExperienceOrb(worldserver, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, j)); ++ worldserver.addEntity(new EntityExperienceOrb(worldserver, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, j, org.bukkit.entity.ExperienceOrb.SpawnReason.BLOCK_BREAK, player)); // Paper + } + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +index 9744d51a52c5eb99c4cf9e36d9380c49674dd136..deaa4c136c23dc6c258cc1ce68523b3c007c80f9 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +@@ -603,7 +603,7 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + int k = EntityExperienceOrb.getOrbValue(j); + + j -= k; +- world.addEntity(new EntityExperienceOrb(world, vec3d.x, vec3d.y, vec3d.z, k)); ++ world.addEntity(new EntityExperienceOrb(world, vec3d.x, vec3d.y, vec3d.z, k, org.bukkit.entity.ExperienceOrb.SpawnReason.FURNACE, entityhuman)); // Paper + } + + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index ccd110c4bfaa263e04154dcc2a5bdbff1f3a7ec2..2513e9a5b66598337f5d380a036ee10fdbab38c3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1835,7 +1835,7 @@ public class CraftWorld implements World { + } else if (TNTPrimed.class.isAssignableFrom(clazz)) { + entity = new EntityTNTPrimed(world, x, y, z, null); + } else if (ExperienceOrb.class.isAssignableFrom(clazz)) { +- entity = new EntityExperienceOrb(world, x, y, z, 0); ++ entity = new EntityExperienceOrb(world, x, y, z, 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null); // Paper + } else if (LightningStrike.class.isAssignableFrom(clazz)) { + entity = EntityTypes.LIGHTNING_BOLT.a(world); + } else if (AreaEffectCloud.class.isAssignableFrom(clazz)) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java +index da14f59aec2b6854c3a47fb531aadc9ddb74954c..c6880830720baa2723ab003e51be1b48574d7319 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java +@@ -20,6 +20,18 @@ public class CraftExperienceOrb extends CraftEntity implements ExperienceOrb { + 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 EntityExperienceOrb getHandle() { + return (EntityExperienceOrb) entity; diff --git a/patches/server-unmapped/0001/0139-Cap-Entity-Collisions.patch b/patches/server-unmapped/0001/0139-Cap-Entity-Collisions.patch new file mode 100644 index 0000000000..94aba4f5af --- /dev/null +++ b/patches/server-unmapped/0001/0139-Cap-Entity-Collisions.patch @@ -0,0 +1,57 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 2dc58b9f769ea43b737804456aafab47ecc143b8..c611b5a63498f5ad1f50a75ccd5d7299e27df7e3 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -324,4 +324,10 @@ public class PaperWorldConfig { + log("Treasure Maps will return already discovered locations"); + } + } ++ ++ public int maxCollisionsPerEntity; ++ private void maxEntityCollision() { ++ maxCollisionsPerEntity = getInt( "max-entity-collisions", this.spigotConfig.getInt("max-entity-collisions", 8) ); ++ log( "Max Entity Collisions: " + maxCollisionsPerEntity ); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 392bb2195e47a665c7be6b1045aa0ae7a509714a..c37e4f4350be3ff2e8e533ed3c4c9fd2c19a2f34 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -268,6 +268,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; + public long activatedTick = Integer.MIN_VALUE; ++ protected int numCollisions = 0; // Paper + public void inactiveTick() { } + // Spigot end + +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index b66b340fdef9423ad8dce290065e028a0c135ea8..aea3a4d73ae8af6e1a17965d6b15ecf7d90b248b 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -2903,8 +2903,11 @@ public abstract class EntityLiving extends Entity { + } + } + +- for (j = 0; j < list.size(); ++j) { ++ numCollisions = Math.max(0, numCollisions - world.paperConfig.maxCollisionsPerEntity); // Paper ++ for (j = 0; j < list.size() && numCollisions < world.paperConfig.maxCollisionsPerEntity; ++j) { // Paper + Entity entity = (Entity) list.get(j); ++ entity.numCollisions++; // Paper ++ numCollisions++; // Paper + + this.C(entity); + } diff --git a/patches/server-unmapped/0001/0140-Remove-CraftScheduler-Async-Task-Debugger.patch b/patches/server-unmapped/0001/0140-Remove-CraftScheduler-Async-Task-Debugger.patch new file mode 100644 index 0000000000..db58842a96 --- /dev/null +++ b/patches/server-unmapped/0001/0140-Remove-CraftScheduler-Async-Task-Debugger.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 5 Feb 2017 00:04:04 -0500 +Subject: [PATCH] Remove CraftScheduler Async Task Debugger + +I have not once ever seen this system help debug a crash. +One report of a suspected memory leak with the system. + +This adds additional overhead to asynchronous task dispatching + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 9b6d9373abb59a30c2835ca891282d07559281f5..0e0f361c3af363539d5d1d865603114bdb84fd67 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -430,7 +430,7 @@ public class CraftScheduler implements BukkitScheduler { + } + parsePending(); + } else { +- debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); ++ //debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper + executor.execute(new ServerSchedulerReportingWrapper(task)); // Paper + // We don't need to parse pending + // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) +@@ -447,7 +447,7 @@ public class CraftScheduler implements BukkitScheduler { + pending.addAll(temp); + temp.clear(); + MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming(); +- debugHead = debugHead.getNextHead(currentTick); ++ //debugHead = debugHead.getNextHead(currentTick); // Paper + } + + private void addTask(final CraftTask task) { +@@ -507,10 +507,15 @@ public class CraftScheduler implements BukkitScheduler { + + @Override + public String toString() { ++ // Paper start ++ return ""; ++ /* + int debugTick = currentTick; + StringBuilder string = new StringBuilder("Recent tasks from ").append(debugTick - RECENT_TICKS).append('-').append(debugTick).append('{'); + debugHead.debugTo(string); + return string.append('}').toString(); ++ */ ++ // Paper end + } + + @Deprecated diff --git a/patches/server-unmapped/0001/0141-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch b/patches/server-unmapped/0001/0141-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch new file mode 100644 index 0000000000..a382dd4190 --- /dev/null +++ b/patches/server-unmapped/0001/0141-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Tue, 7 Feb 2017 16:55:35 -0600 +Subject: [PATCH] Make targetSize more aggressive in the chunk unload queue + + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index e8150c456efe72a561d6a6a7647eca05fbc8bd94..56f83a930c3dad1a1de366bff530131d92b4893c 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -121,7 +121,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + private final PlayerMap playerMap; + public final Int2ObjectMap trackedEntities; + private final Long2ByteMap z; +- private final Queue A; ++ private final Queue A; private final Queue getUnloadQueueTasks() { return this.A; } // Paper - OBFHELPER + private int viewDistance; + + // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() +@@ -179,7 +179,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.playerMap = new PlayerMap(); + this.trackedEntities = new Int2ObjectOpenHashMap(); + this.z = new Long2ByteOpenHashMap(); +- this.A = Queues.newConcurrentLinkedQueue(); ++ this.A = new com.destroystokyo.paper.utils.CachedSizeConcurrentLinkedQueue<>(); // Paper - need constant-time size() + this.definedStructureManager = definedstructuremanager; + this.w = convertable_conversionsession.a(worldserver.getDimensionKey()); + this.world = worldserver; +@@ -437,7 +437,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + // Spigot start + org.spigotmc.SlackActivityAccountant activityAccountant = this.world.getMinecraftServer().slackActivityAccountant; + activityAccountant.startActivity(0.5); +- int targetSize = (int) (this.unloadQueue.size() * UNLOAD_QUEUE_RESIZE_FACTOR); ++ int targetSize = Math.min(this.unloadQueue.size() - 100, (int) (this.unloadQueue.size() * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive + // Spigot end + while (longiterator.hasNext()) { // Spigot + long j = longiterator.nextLong(); +@@ -459,7 +459,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + Runnable runnable; + +- while ((booleansupplier.getAsBoolean() || this.A.size() > 2000) && (runnable = (Runnable) this.A.poll()) != null) { ++ int queueTarget = Math.min(this.getUnloadQueueTasks().size() - 100, (int) (this.getUnloadQueueTasks().size() * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Target this queue as well ++ while ((booleansupplier.getAsBoolean() || this.getUnloadQueueTasks().size() > queueTarget) && (runnable = (Runnable)this.getUnloadQueueTasks().poll()) != null) { // Paper - Target this queue as well + runnable.run(); + } + diff --git a/patches/server-unmapped/0001/0142-Do-not-let-armorstands-drown.patch b/patches/server-unmapped/0001/0142-Do-not-let-armorstands-drown.patch new file mode 100644 index 0000000000..a8f2fa7020 --- /dev/null +++ b/patches/server-unmapped/0001/0142-Do-not-let-armorstands-drown.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 18 Feb 2017 19:29:58 -0600 +Subject: [PATCH] Do not let armorstands drown + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index aea3a4d73ae8af6e1a17965d6b15ecf7d90b248b..c17c0e1570d6ee2498d3f643df90a94e49ee2e3e 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -338,6 +338,7 @@ public abstract class EntityLiving extends Entity { + super.a(d0, flag, iblockdata, blockposition); + } + ++ public boolean canBreatheUnderwater() { return this.cM(); } // Paper - OBFHELPER + public boolean cM() { + return this.getMonsterType() == EnumMonsterType.UNDEAD; + } +@@ -381,7 +382,7 @@ public abstract class EntityLiving extends Entity { + + if (this.isAlive()) { + if (this.a((Tag) TagsFluid.WATER) && !this.world.getType(new BlockPosition(this.locX(), this.getHeadY(), this.locZ())).a(Blocks.BUBBLE_COLUMN)) { +- if (!this.cM() && !MobEffectUtil.c(this) && !flag1) { ++ if (!this.canBreatheUnderwater() && !MobEffectUtil.c(this) && !flag1) { // Paper - use OBFHELPER so it can be overridden + this.setAirTicks(this.l(this.getAirTicks())); + if (this.getAirTicks() == -20) { + this.setAirTicks(0); +diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +index 2ef991aa7f739d3577fbbf4386064557e8f7c904..57e0ea95df34fab22d6c5868ab839d56a3fa85fc 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +@@ -872,5 +872,10 @@ public class EntityArmorStand extends EntityLiving { + super.move(moveType, vec3d); + } + } ++ ++ @Override ++ public boolean canBreatheUnderwater() { // Skips a bit of damage handling code, probably a micro-optimization ++ return true; ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0143-Properly-handle-async-calls-to-restart-the-server.patch b/patches/server-unmapped/0001/0143-Properly-handle-async-calls-to-restart-the-server.patch new file mode 100644 index 0000000000..5d64df129e --- /dev/null +++ b/patches/server-unmapped/0001/0143-Properly-handle-async-calls-to-restart-the-server.patch @@ -0,0 +1,307 @@ +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 283c1111d99b6ae09b6db0c0079eeb0f1cbb7b2b..d92ca78e483b3f085e3bad1d1250cac2f9031fa7 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -201,6 +201,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant, WorldServer> worldServer; + private PlayerList playerList; + private volatile boolean isRunning; ++ private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart + private boolean isStopped; + private int ticks; + protected final Proxy proxy; +@@ -860,7 +861,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 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 ( EntityPlayer p : (List) MinecraftServer.getServer().getPlayerList().players ) +- { +- p.playerConnection.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().getServerConnection().b(); ++ // Paper start - sync copied from above with minor changes, async added ++ private static void shutdownServer(boolean isRestarting) ++ { ++ if ( MinecraftServer.getServer().isMainThread() ) ++ { ++ // Kick all players ++ for ( EntityPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) ++ { ++ p.playerConnection.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().getServerConnection().b(); ++ ++ // 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-unmapped/0001/0144-Add-system-property-to-disable-book-size-limits.patch b/patches/server-unmapped/0001/0144-Add-system-property-to-disable-book-size-limits.patch new file mode 100644 index 0000000000..e6a89f656d --- /dev/null +++ b/patches/server-unmapped/0001/0144-Add-system-property-to-disable-book-size-limits.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 13 May 2017 20:11:21 -0500 +Subject: [PATCH] Add system property to disable book size limits + +If anyone comes in with a watchdog crash related to books after this patch +you will not only be publicly shamed but also made an example of. + +Disables the security limits on books entirely, allowing plugins AND players +to make books with as much data as they want. Do not use this without +limiting incoming data from packets in some other way. + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +index af1f45bb8bbe380eec1dcdef1beacb06c9d932a8..418b5353cae3fa2ada948b416a3fd7422101ae5c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +@@ -40,6 +40,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + static final int MAX_PAGES = 100; + static final int MAX_PAGE_LENGTH = 320; // 256 limit + 64 characters to allow for psuedo colour codes + static final int MAX_TITLE_LENGTH = 32; ++ private static final boolean OVERRIDE_CHECKS = Boolean.getBoolean("disable.book-limits"); // Paper - Add override + + protected String title; + protected String author; +@@ -242,7 +243,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + if (title == null) { + this.title = null; + return true; +- } else if (title.length() > MAX_TITLE_LENGTH) { ++ } else if (title.length() > MAX_TITLE_LENGTH && !OVERRIDE_CHECKS) { // Paper - Add override + return false; + } + +@@ -433,7 +434,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + String validatePage(String page) { + if (page == null) { + page = ""; +- } else if (page.length() > MAX_PAGE_LENGTH) { ++ } else if (page.length() > MAX_PAGE_LENGTH && !OVERRIDE_CHECKS) { // Paper - Add override + page = page.substring(0, MAX_PAGE_LENGTH); + } + return page; +@@ -443,7 +444,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + // asserted: page != null + if (this.pages == null) { + this.pages = new ArrayList(); +- } else if (this.pages.size() >= MAX_PAGES) { ++ } else if (this.pages.size() >= MAX_PAGES && !OVERRIDE_CHECKS) {// Paper - Add override + return; + } + this.pages.add(page); diff --git a/patches/server-unmapped/0001/0145-Add-option-to-make-parrots-stay-on-shoulders-despite.patch b/patches/server-unmapped/0001/0145-Add-option-to-make-parrots-stay-on-shoulders-despite.patch new file mode 100644 index 0000000000..270c71d6d4 --- /dev/null +++ b/patches/server-unmapped/0001/0145-Add-option-to-make-parrots-stay-on-shoulders-despite.patch @@ -0,0 +1,58 @@ +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? + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index c611b5a63498f5ad1f50a75ccd5d7299e27df7e3..9d1cddc6038f0fd0286e4a32013ae98ff0b00dd1 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -330,4 +330,10 @@ public class PaperWorldConfig { + maxCollisionsPerEntity = getInt( "max-entity-collisions", this.spigotConfig.getInt("max-entity-collisions", 8) ); + log( "Max Entity Collisions: " + maxCollisionsPerEntity ); + } ++ ++ public boolean parrotsHangOnBetter; ++ private void parrotsHangOnBetter() { ++ parrotsHangOnBetter = getBoolean("parrots-are-unaffected-by-player-movement", false); ++ log("Parrots are unaffected by player movement: " + parrotsHangOnBetter); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 8a5a1ca08073d1a4df949a53fbf7e75339bf2761..bf3195eaf1fc7bdddafbb54d0ccf825e95b63210 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -2052,6 +2052,13 @@ public class PlayerConnection implements PacketListenerPlayIn { + switch (packetplayinentityaction.c()) { + case PRESS_SHIFT_KEY: + this.player.setSneaking(true); ++ ++ // Paper start - Hang on! ++ if (this.player.world.paperConfig.parrotsHangOnBetter) { ++ this.player.releaseShoulderEntities(); ++ } ++ // Paper end ++ + break; + case RELEASE_SHIFT_KEY: + this.player.setSneaking(false); +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index e3e3426a00128b56d523bb43a59b814b915ad0ff..ba26fc2405e17d582da971d03147fb1865e9b546 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -532,7 +532,7 @@ public abstract class EntityHuman extends EntityLiving { + this.j(this.getShoulderEntityLeft()); + this.j(this.getShoulderEntityRight()); + if (!this.world.isClientSide && (this.fallDistance > 0.5F || this.isInWater()) || this.abilities.isFlying || this.isSleeping()) { +- this.releaseShoulderEntities(); ++ if (!this.world.paperConfig.parrotsHangOnBetter) this.releaseShoulderEntities(); // Paper - Hang on! + } + + } diff --git a/patches/server-unmapped/0001/0146-Add-configuration-option-to-prevent-player-names-fro.patch b/patches/server-unmapped/0001/0146-Add-configuration-option-to-prevent-player-names-fro.patch new file mode 100644 index 0000000000..fbf90f8f51 --- /dev/null +++ b/patches/server-unmapped/0001/0146-Add-configuration-option-to-prevent-player-names-fro.patch @@ -0,0 +1,36 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 4e2f243faa209925dcb7c3ef89df3ed875c5ff78..48319aaf1c525c6fb7bdee5c2f570a0d056d4eae 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -268,4 +268,9 @@ public class PaperConfig { + flyingKickPlayerMessage = getString("messages.kick.flying-player", flyingKickPlayerMessage); + flyingKickVehicleMessage = getString("messages.kick.flying-vehicle", flyingKickVehicleMessage); + } ++ ++ public static boolean suggestPlayersWhenNullTabCompletions = true; ++ private static void suggestPlayersWhenNull() { ++ suggestPlayersWhenNullTabCompletions = getBoolean("settings.suggest-player-names-when-null-tab-completions", suggestPlayersWhenNullTabCompletions); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index d3621b626799f470329e8f5097fc10016cd48560..1f0021c5374e1af9c9cd29d44e6b0bd9522394d9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2292,5 +2292,10 @@ public final class CraftServer implements Server { + commandMap.registerServerAliases(); + return true; + } ++ ++ @Override ++ public boolean suggestPlayerNamesWhenNullTabCompletions() { ++ return com.destroystokyo.paper.PaperConfig.suggestPlayersWhenNullTabCompletions; ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0147-Use-TerminalConsoleAppender-for-console-improvements.patch b/patches/server-unmapped/0001/0147-Use-TerminalConsoleAppender-for-console-improvements.patch new file mode 100644 index 0000000000..d0f943e421 --- /dev/null +++ b/patches/server-unmapped/0001/0147-Use-TerminalConsoleAppender-for-console-improvements.patch @@ -0,0 +1,541 @@ +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. + +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 + +diff --git a/pom.xml b/pom.xml +index 5710dd02c80bc713e5dd289c9aa4bc21fd0f4318..e0fcbe1b13a5ec1244d6d904ca76336a47967c9b 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -57,10 +57,26 @@ + compile + + +- jline +- jline +- 2.12.1 +- compile ++ net.minecrell ++ terminalconsoleappender ++ 1.2.0 ++ ++ ++ org.jline ++ jline-terminal-jansi ++ 3.12.1 ++ runtime ++ ++ ++ ++ org.apache.logging.log4j ++ log4j-core ++ runtime + + + org.apache.logging.log4j +@@ -276,10 +292,18 @@ + + META-INF/services/java.sql.Driver + ++ + + + + ++ ++ ++ com.github.edwgiz ++ maven-shade-plugin.log4j2-cachefile-transformer ++ 2.13.1 ++ ++ + + + org.apache.maven.plugins +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..89eeb9d202405747409e65fcf226d95379987e29 +--- /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.issueCommand(command, this.server.getServerCommandListener()); ++ } ++ ++ @Override ++ protected void shutdown() { ++ this.server.safeShutdown(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..685deaa0e5d1ddc13e3a7c0471b1cfcf1710c869 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java +@@ -0,0 +1,17 @@ ++package com.destroystokyo.paper.console; ++ ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++import org.bukkit.craftbukkit.command.CraftConsoleCommandSender; ++ ++public class TerminalConsoleCommandSender extends CraftConsoleCommandSender { ++ ++ private static final Logger LOGGER = LogManager.getRootLogger(); ++ ++ @Override ++ public void sendRawMessage(String message) { ++ // TerminalConsoleAppender supports color codes directly in log messages ++ LOGGER.info(message); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index d92ca78e483b3f085e3bad1d1250cac2f9031fa7..62ee4708a3196cfa395317a6312b3ac6c036793a 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -160,7 +160,7 @@ import org.apache.logging.log4j.Logger; + import com.mojang.serialization.DynamicOps; + import com.mojang.serialization.Lifecycle; + import com.google.common.collect.ImmutableSet; +-import jline.console.ConsoleReader; ++// import jline.console.ConsoleReader; // Paper + import joptsimple.OptionSet; + import net.minecraft.nbt.DynamicOpsNBT; + import net.minecraft.nbt.NBTBase; +@@ -255,7 +255,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; +@@ -324,7 +324,9 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 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 befcc19f9b56df9096b98a23b0020f1db793ea5b..a957695457cf3252848ce6ef37069692841b8e28 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -4,20 +4,31 @@ 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() { +@@ -29,25 +40,37 @@ public class ConsoleCommandCompleter implements Completer { + 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 70f8d42992aa348ef7b2d03d22cdd59d7c73f0fe..449e99d1b673870ed6892f6ab2c715a2db35c35d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +@@ -17,7 +17,7 @@ public class ServerShutdownThread extends Thread { + server.close(); + } finally { + try { +- 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 99564fed7ce77e29dbdc591bcfe656af741acf8a..9a2da548b8860b496e396564b2c8f6383f020193 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..620b9490e5f159080e50289d127404a1b56adbef 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-unmapped/0001/0148-provide-a-configurable-option-to-disable-creeper-lin.patch b/patches/server-unmapped/0001/0148-provide-a-configurable-option-to-disable-creeper-lin.patch new file mode 100644 index 0000000000..d2c710ef94 --- /dev/null +++ b/patches/server-unmapped/0001/0148-provide-a-configurable-option-to-disable-creeper-lin.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 11 Jun 2017 21:01:18 +0100 +Subject: [PATCH] provide a configurable option to disable creeper lingering + effect spawns + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 9d1cddc6038f0fd0286e4a32013ae98ff0b00dd1..90ca51dfdbb3045dd528450225cba96f5834166e 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -336,4 +336,10 @@ public class PaperWorldConfig { + parrotsHangOnBetter = getBoolean("parrots-are-unaffected-by-player-movement", false); + log("Parrots are unaffected by player movement: " + parrotsHangOnBetter); + } ++ ++ public boolean disableCreeperLingeringEffect; ++ private void setDisableCreeperLingeringEffect() { ++ disableCreeperLingeringEffect = getBoolean("disable-creeper-lingering-effect", false); ++ log("Creeper lingering effect: " + disableCreeperLingeringEffect); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java b/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java +index 4106cce3071ac8bfe33e94b0fc7f67cfeef214b4..08292753f925f33d75f3aca835c1fd19494b22ec 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java +@@ -262,7 +262,7 @@ public class EntityCreeper extends EntityMonster { + private void createEffectCloud() { + Collection collection = this.getEffects(); + +- if (!collection.isEmpty()) { ++ if (!collection.isEmpty() && !world.paperConfig.disableCreeperLingeringEffect) { // Paper + EntityAreaEffectCloud entityareaeffectcloud = new EntityAreaEffectCloud(this.world, this.locX(), this.locY(), this.locZ()); + + entityareaeffectcloud.setSource(this); // CraftBukkit diff --git a/patches/server-unmapped/0001/0149-Item-canEntityPickup.patch b/patches/server-unmapped/0001/0149-Item-canEntityPickup.patch new file mode 100644 index 0000000000..3aa1baa985 --- /dev/null +++ b/patches/server-unmapped/0001/0149-Item-canEntityPickup.patch @@ -0,0 +1,55 @@ +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/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +index 6ee5e1b0bb34ba490a130fbcbdb7a2706c5ecf86..c22b5f8fcdd4aa7dac242f634ef73edcd8745fc6 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java ++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +@@ -607,6 +607,11 @@ public abstract class EntityInsentient extends EntityLiving { + EntityItem entityitem = (EntityItem) iterator.next(); + + if (!entityitem.dead && !entityitem.getItemStack().isEmpty() && !entityitem.p() && this.i(entityitem.getItemStack())) { ++ // Paper Start ++ if (!entityitem.canMobPickup) { ++ continue; ++ } ++ // Paper End + this.b(entityitem); + } + } +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityItem.java b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +index 38b91b450c6bdc25efaed495ce26d909a52531bf..c486c07a358c0b444e1a1372b6d0fa2901840eac 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityItem.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +@@ -47,6 +47,7 @@ public class EntityItem extends Entity { + private UUID owner; + public final float b; + private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit ++ public boolean canMobPickup = true; // Paper + + public EntityItem(EntityTypes entitytypes, World world) { + super(entitytypes, world); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +index f333dd2726f554a3137a3a78d162905f4ed1435e..5988cdd18b7e4bfca0075fd2356cfe9c4e673954 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +@@ -49,6 +49,16 @@ public class CraftItem extends CraftEntity implements Item { + item.age = value; + } + ++ // Paper Start ++ public boolean canMobPickup() { ++ return item.canMobPickup; ++ } ++ ++ public void setCanMobPickup(boolean canMobPickup) { ++ item.canMobPickup = canMobPickup; ++ } ++ // Paper End ++ + @Override + public void setOwner(UUID uuid) { + item.setOwner(uuid); diff --git a/patches/server-unmapped/0001/0150-PlayerPickupItemEvent-setFlyAtPlayer.patch b/patches/server-unmapped/0001/0150-PlayerPickupItemEvent-setFlyAtPlayer.patch new file mode 100644 index 0000000000..2bbbc7656e --- /dev/null +++ b/patches/server-unmapped/0001/0150-PlayerPickupItemEvent-setFlyAtPlayer.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 7 May 2017 06:26:09 -0500 +Subject: [PATCH] PlayerPickupItemEvent#setFlyAtPlayer + + +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityItem.java b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +index c486c07a358c0b444e1a1372b6d0fa2901840eac..3b72032f6f4f4bd64d1202658cc0c6ee4fb76ea9 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityItem.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +@@ -363,6 +363,7 @@ public class EntityItem extends Entity { + // CraftBukkit start - fire PlayerPickupItemEvent + int canHold = entityhuman.inventory.canHold(itemstack); + int remaining = i - canHold; ++ boolean flyAtPlayer = false; // Paper + + if (this.pickupDelay <= 0 && canHold > 0) { + itemstack.setCount(canHold); +@@ -370,8 +371,14 @@ public class EntityItem extends Entity { + PlayerPickupItemEvent playerEvent = new PlayerPickupItemEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining); + playerEvent.setCancelled(!entityhuman.canPickUpLoot); + this.world.getServer().getPluginManager().callEvent(playerEvent); ++ flyAtPlayer = playerEvent.getFlyAtPlayer(); // Paper + if (playerEvent.isCancelled()) { + itemstack.setCount(i); // SPIGOT-5294 - restore count ++ // Paper Start ++ if (flyAtPlayer) { ++ entityhuman.receive(this, i); ++ } ++ // Paper End + return; + } + +@@ -401,7 +408,11 @@ public class EntityItem extends Entity { + // CraftBukkit end + + if (this.pickupDelay == 0 && (this.owner == null || this.owner.equals(entityhuman.getUniqueID())) && entityhuman.inventory.pickup(itemstack)) { +- entityhuman.receive(this, i); ++ // Paper Start ++ if (flyAtPlayer) { ++ entityhuman.receive(this, i); ++ } ++ // Paper End + if (itemstack.isEmpty()) { + this.die(); + itemstack.setCount(i); diff --git a/patches/server-unmapped/0001/0151-PlayerAttemptPickupItemEvent.patch b/patches/server-unmapped/0001/0151-PlayerAttemptPickupItemEvent.patch new file mode 100644 index 0000000000..13d2f54354 --- /dev/null +++ b/patches/server-unmapped/0001/0151-PlayerAttemptPickupItemEvent.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 11 Jun 2017 16:30:30 -0500 +Subject: [PATCH] PlayerAttemptPickupItemEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityItem.java b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +index 3b72032f6f4f4bd64d1202658cc0c6ee4fb76ea9..de11fd9772f30ac72c3ca52ec4efc3fef4091425 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityItem.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +@@ -36,6 +36,7 @@ import net.minecraft.server.MinecraftServer; + import org.bukkit.event.entity.EntityPickupItemEvent; + import org.bukkit.event.player.PlayerPickupItemEvent; + // CraftBukkit end ++import org.bukkit.event.player.PlayerAttemptPickupItemEvent; // Paper + + public class EntityItem extends Entity { + +@@ -365,6 +366,22 @@ public class EntityItem extends Entity { + int remaining = i - canHold; + boolean flyAtPlayer = false; // Paper + ++ // Paper start ++ if (this.pickupDelay <= 0) { ++ PlayerAttemptPickupItemEvent attemptEvent = new PlayerAttemptPickupItemEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining); ++ this.world.getServer().getPluginManager().callEvent(attemptEvent); ++ ++ flyAtPlayer = attemptEvent.getFlyAtPlayer(); ++ if (attemptEvent.isCancelled()) { ++ if (flyAtPlayer) { ++ entityhuman.receive(this, i); ++ } ++ ++ return; ++ } ++ } ++ // Paper end ++ + if (this.pickupDelay <= 0 && canHold > 0) { + itemstack.setCount(canHold); + // Call legacy event diff --git a/patches/server-unmapped/0001/0152-Add-UnknownCommandEvent.patch b/patches/server-unmapped/0001/0152-Add-UnknownCommandEvent.patch new file mode 100644 index 0000000000..9c039a3e69 --- /dev/null +++ b/patches/server-unmapped/0001/0152-Add-UnknownCommandEvent.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sweepyoface +Date: Sat, 17 Jun 2017 18:48:21 -0400 +Subject: [PATCH] Add UnknownCommandEvent + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 5d357b0f84b5242066dcce203752a0f46e9a249c..4f9c42a4b0256f181263bf5e0492714a01fbec38 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -104,6 +104,7 @@ import net.minecraft.world.level.storage.WorldNBTStorage; + import net.minecraft.world.level.storage.loot.LootTableRegistry; + import net.minecraft.world.phys.Vec3D; + import org.apache.commons.lang.Validate; ++import org.apache.commons.lang3.StringUtils; + import org.bukkit.BanList; + import org.bukkit.Bukkit; + import org.bukkit.ChatColor; +@@ -177,6 +178,7 @@ import org.bukkit.craftbukkit.util.Versioning; + import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions; + import org.bukkit.entity.Entity; + import org.bukkit.entity.Player; ++import org.bukkit.event.command.UnknownCommandEvent; // Paper + import org.bukkit.event.inventory.InventoryType; + import org.bukkit.event.player.PlayerChatTabCompleteEvent; + import org.bukkit.event.server.BroadcastMessageEvent; +@@ -793,7 +795,13 @@ public final class CraftServer implements Server { + + // Spigot start + if (!org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty()) { +- sender.sendMessage(org.spigotmc.SpigotConfig.unknownCommandMessage); ++ // Paper start ++ UnknownCommandEvent event = new UnknownCommandEvent(sender, commandLine, org.spigotmc.SpigotConfig.unknownCommandMessage); ++ Bukkit.getServer().getPluginManager().callEvent(event); ++ if (StringUtils.isNotEmpty(event.getMessage())) { ++ sender.sendMessage(event.getMessage()); ++ } ++ // Paper end + } + // Spigot end + diff --git a/patches/server-unmapped/0001/0153-Basic-PlayerProfile-API.patch b/patches/server-unmapped/0001/0153-Basic-PlayerProfile-API.patch new file mode 100644 index 0000000000..177be589bb --- /dev/null +++ b/patches/server-unmapped/0001/0153-Basic-PlayerProfile-API.patch @@ -0,0 +1,575 @@ +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 + +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..040fd0c3fc4bc3a04fe5dff919a41fe9b474708e +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java +@@ -0,0 +1,301 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.destroystokyo.paper.PaperConfig; ++import com.google.common.base.Charsets; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import com.mojang.authlib.properties.PropertyMap; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.players.UserCache; ++import org.apache.commons.lang3.Validate; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.spigotmc.SpigotConfig; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import java.util.AbstractSet; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.Objects; ++import java.util.Set; ++import java.util.UUID; ++ ++public class CraftPlayerProfile implements PlayerProfile { ++ ++ private GameProfile profile; ++ private final PropertySet properties = new PropertySet(); ++ ++ public CraftPlayerProfile(CraftPlayer player) { ++ this.profile = player.getHandle().getProfile(); ++ } ++ ++ public CraftPlayerProfile(UUID id, String name) { ++ this.profile = new GameProfile(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())); ++ } ++ ++ public GameProfile getGameProfile() { ++ return profile; ++ } ++ ++ @Nullable ++ @Override ++ public UUID getId() { ++ return profile.getId(); ++ } ++ ++ @Override ++ public UUID setId(@Nullable UUID uuid) { ++ GameProfile prev = this.profile; ++ this.profile = new GameProfile(uuid, prev.getName()); ++ copyProfileProperties(prev, this.profile); ++ return prev.getId(); ++ } ++ ++ @Nullable ++ @Override ++ public String getName() { ++ return profile.getName(); ++ } ++ ++ @Override ++ public String setName(@Nullable String name) { ++ GameProfile prev = this.profile; ++ this.profile = new GameProfile(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(); ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ CraftPlayerProfile that = (CraftPlayerProfile) o; ++ return Objects.equals(profile, that.profile); ++ } ++ ++ @Override ++ public int hashCode() { ++ return profile.hashCode(); ++ } ++ ++ @Override ++ public String toString() { ++ return profile.toString(); ++ } ++ ++ @Override ++ public CraftPlayerProfile clone() { ++ CraftPlayerProfile clone = new CraftPlayerProfile(this.getId(), this.getName()); ++ clone.setProperties(getProperties()); ++ return clone; ++ } ++ ++ @Override ++ public boolean isComplete() { ++ return profile.isComplete(); ++ } ++ ++ @Override ++ public boolean completeFromCache() { ++ MinecraftServer server = MinecraftServer.getServer(); ++ return completeFromCache(false, server.getOnlineMode() || (SpigotConfig.bungee && PaperConfig.bungeeOnlineMode)); ++ } ++ ++ public boolean completeFromCache(boolean onlineMode) { ++ return completeFromCache(false, onlineMode); ++ } ++ ++ public boolean completeFromCache(boolean lookupUUID, boolean onlineMode) { ++ MinecraftServer server = MinecraftServer.getServer(); ++ String name = profile.getName(); ++ UserCache userCache = server.getUserCache(); ++ if (profile.getId() == null) { ++ final GameProfile profile; ++ if (onlineMode) { ++ profile = lookupUUID ? userCache.getProfile(name) : 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() == null || !hasTextures()) && profile.getId() != null) { ++ GameProfile profile = userCache.getProfile(this.profile.getId()); ++ if (profile != null) { ++ if (this.profile.getName() == 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; ++ } else { ++ copyProfileProperties(profile, this.profile); ++ } ++ } ++ } ++ return this.profile.isComplete(); ++ } ++ ++ public boolean complete(boolean textures) { ++ MinecraftServer server = MinecraftServer.getServer(); ++ return complete(textures, server.getOnlineMode() || (SpigotConfig.bungee && PaperConfig.bungeeOnlineMode)); ++ } ++ public boolean complete(boolean textures, boolean onlineMode) { ++ MinecraftServer server = MinecraftServer.getServer(); ++ ++ boolean isCompleteFromCache = this.completeFromCache(true, onlineMode); ++ if (onlineMode && (!isCompleteFromCache || textures && !hasTextures())) { ++ GameProfile result = server.getMinecraftSessionService().fillProfileProperties(profile, true); ++ if (result != null) { ++ copyProfileProperties(result, this.profile, true); ++ } ++ if (this.profile.isComplete()) { ++ server.getUserCache().saveProfile(this.profile); ++ } ++ } ++ return profile.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.getName()); ++ targetProperties.put(property.getName(), property); ++ } ++ } ++ ++ private static ProfileProperty toBukkit(Property property) { ++ return new ProfileProperty(property.getName(), property.getValue(), property.getSignature()); ++ } ++ ++ 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(); ++ } ++ ++ 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..d64d45eb01c65864fca1077982d89bc05e0f811b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java +@@ -0,0 +1,31 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.*; ++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 = (Environment)EnvironmentParser.getEnvironmentFromProperties().orElse(YggdrasilEnvironment.PROD);; ++ } ++ ++ @Override ++ public UserAuthentication createUserAuthentication(Agent agent) { ++ return new PaperUserAuthentication(this, agent); ++ } ++ ++ @Override ++ public MinecraftSessionService createMinecraftSessionService() { ++ return new PaperMinecraftSessionService(this, this.environment); ++ } ++ ++ @Override ++ public GameProfileRepository createProfileRepository() { ++ return new PaperGameProfileRepository(this, 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..582c169c85ac66f1f9430f79042e4655f776c157 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java +@@ -0,0 +1,18 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Agent; ++import com.mojang.authlib.Environment; ++import com.mojang.authlib.ProfileLookupCallback; ++import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; ++import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; ++ ++public class PaperGameProfileRepository extends YggdrasilGameProfileRepository { ++ public PaperGameProfileRepository(YggdrasilAuthenticationService authenticationService, Environment environment) { ++ super(authenticationService, environment); ++ } ++ ++ @Override ++ public void findProfilesByNames(String[] names, Agent agent, ProfileLookupCallback callback) { ++ super.findProfilesByNames(names, agent, 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..93d73c27340645c7502acafdc0b2cfbc1a759dd8 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +@@ -0,0 +1,30 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Environment; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.minecraft.MinecraftProfileTexture; ++import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; ++import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; ++ ++import java.util.Map; ++ ++public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionService { ++ protected PaperMinecraftSessionService(YggdrasilAuthenticationService authenticationService, Environment environment) { ++ super(authenticationService, environment); ++ } ++ ++ @Override ++ public Map getTextures(GameProfile profile, boolean requireSecure) { ++ return super.getTextures(profile, requireSecure); ++ } ++ ++ @Override ++ public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) { ++ return super.fillProfileProperties(profile, requireSecure); ++ } ++ ++ @Override ++ protected GameProfile fillGameProfile(GameProfile profile, boolean requireSecure) { ++ return super.fillGameProfile(profile, requireSecure); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java b/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3cdd06d3af7ff94f1fe1a11b9a9275e17c695a38 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java +@@ -0,0 +1,12 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Agent; ++import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; ++import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication; ++import java.util.UUID; ++ ++public class PaperUserAuthentication extends YggdrasilUserAuthentication { ++ public PaperUserAuthentication(YggdrasilAuthenticationService authenticationService, Agent agent) { ++ super(authenticationService, UUID.randomUUID().toString(), agent); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 18b56b59fd6efd618e6ff6f9cf3a02f57588d244..cd7dc7d90efddb8a1bb50cd964b43d18cf9c83d1 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -1,6 +1,8 @@ + package net.minecraft.server; + + import com.destroystokyo.paper.block.TargetBlockInfo; ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; + import com.google.common.util.concurrent.ThreadFactoryBuilder; + import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; + import net.minecraft.core.BlockPosition; +@@ -11,6 +13,7 @@ import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.RayTrace; + import net.minecraft.world.level.World; + 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; +@@ -345,6 +348,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 89db31061fcc3420bc8e668533a4051cdbd12253..191a74bd9b894f9d64d0a55747cb17e07ceef597 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -132,7 +132,7 @@ public class Main { + } + + File file = (File) optionset.valueOf("universe"); // CraftBukkit +- YggdrasilAuthenticationService yggdrasilauthenticationservice = new YggdrasilAuthenticationService(Proxy.NO_PROXY); ++ YggdrasilAuthenticationService yggdrasilauthenticationservice = new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY); // Paper + MinecraftSessionService minecraftsessionservice = yggdrasilauthenticationservice.createMinecraftSessionService(); + GameProfileRepository gameprofilerepository = yggdrasilauthenticationservice.createProfileRepository(); + UserCache usercache = new UserCache(gameprofilerepository, new File(file, MinecraftServer.b.getName())); +diff --git a/src/main/java/net/minecraft/server/players/UserCache.java b/src/main/java/net/minecraft/server/players/UserCache.java +index 4ad084e7cea3b341ca0dbaa6e853cfc685a555ff..e17927ecc3ad3e27e436082ac94e3772d7311725 100644 +--- a/src/main/java/net/minecraft/server/players/UserCache.java ++++ b/src/main/java/net/minecraft/server/players/UserCache.java +@@ -45,7 +45,7 @@ public class UserCache { + + private static final Logger LOGGER = LogManager.getLogger(); + private static boolean b; +- private final Map c = Maps.newConcurrentMap(); ++ private final Map c = Maps.newConcurrentMap();private final Map nameCache = c; // Paper - OBFHELPER // Paper + private final Map d = Maps.newConcurrentMap(); + private final GameProfileRepository e; + private final Gson f = (new GsonBuilder()).create(); +@@ -109,6 +109,7 @@ public class UserCache { + return UserCache.b; + } + ++ public void saveProfile(GameProfile gameprofile) { a(gameprofile); } // Paper - OBFHELPER + public synchronized void a(GameProfile gameprofile) { // Paper - synchronize + Calendar calendar = Calendar.getInstance(); + +@@ -158,6 +159,13 @@ public class UserCache { + return gameprofile; + } + ++ // Paper start ++ @Nullable public GameProfile getProfileIfCached(String name) { ++ UserCache.UserCacheEntry entry = this.nameCache.get(name.toLowerCase(Locale.ROOT)); ++ return entry == null ? null : entry.getProfile(); ++ } ++ // Paper end ++ + @Nullable + public GameProfile getProfile(UUID uuid) { + UserCache.UserCacheEntry usercache_usercacheentry = (UserCache.UserCacheEntry) this.d.get(uuid); +@@ -340,7 +348,7 @@ public class UserCache { + + static class UserCacheEntry { + +- private final GameProfile a; ++ private final GameProfile a;public GameProfile getProfile() { return a; } // Paper - OBFHELPER + private final Date b; + private volatile long c; + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 4f9c42a4b0256f181263bf5e0492714a01fbec38..7d2fc05ddb18369aed29595e3c0dcbf6db136309 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -227,6 +227,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; +@@ -2309,5 +2312,24 @@ public final class CraftServer implements Server { + public boolean suggestPlayerNamesWhenNullTabCompletions() { + return com.destroystokyo.paper.PaperConfig.suggestPlayersWhenNullTabCompletions; + } ++ ++ @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); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +index dff67a48961399f3746f99b4f2363724bfe51c36..8298ae9bf1c5635f08552c15f004b3d0f6e9f19b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +@@ -80,6 +80,13 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + } + + private void setProfile(GameProfile profile) { ++ // Paper start ++ if (profile != null) { ++ com.destroystokyo.paper.profile.CraftPlayerProfile paperProfile = new com.destroystokyo.paper.profile.CraftPlayerProfile(profile); ++ paperProfile.completeFromCache(false, true); ++ profile = paperProfile.getGameProfile(); ++ } ++ // Paper end + this.profile = profile; + this.serializedProfile = (profile == null) ? null : GameProfileSerializer.serialize(new NBTTagCompound(), profile); + } diff --git a/patches/server-unmapped/0001/0154-Shoulder-Entities-Release-API.patch b/patches/server-unmapped/0001/0154-Shoulder-Entities-Release-API.patch new file mode 100644 index 0000000000..ac464d6b5a --- /dev/null +++ b/patches/server-unmapped/0001/0154-Shoulder-Entities-Release-API.patch @@ -0,0 +1,96 @@ +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/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index ba26fc2405e17d582da971d03147fb1865e9b546..63e8062ae3f3407b92b72b5fccaa958c39282fb8 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -1880,20 +1880,44 @@ public abstract class EntityHuman extends EntityLiving { + + } + ++ // Paper start ++ public Entity releaseLeftShoulderEntity() { ++ Entity entity = this.spawnEntityFromShoulder0(this.getShoulderEntityLeft()); ++ if (entity != null) { ++ this.setShoulderEntityLeft(new NBTTagCompound()); ++ } ++ return entity; ++ } ++ ++ public Entity releaseRightShoulderEntity() { ++ Entity entity = this.spawnEntityFromShoulder0(this.getShoulderEntityRight()); ++ if (entity != null) { ++ this.setShoulderEntityRight(new NBTTagCompound()); ++ } ++ return entity; ++ } ++ // Paper - maintain old signature + private boolean spawnEntityFromShoulder(NBTTagCompound nbttagcompound) { // CraftBukkit void->boolean +- if (!this.world.isClientSide && !nbttagcompound.isEmpty()) { ++ return spawnEntityFromShoulder0(nbttagcompound) != null; ++ } ++ ++ // Paper - return entity ++ private Entity spawnEntityFromShoulder0(@Nullable NBTTagCompound nbttagcompound) { ++ if (!this.world.isClientSide && nbttagcompound != null && !nbttagcompound.isEmpty()) { + return EntityTypes.a(nbttagcompound, this.world).map((entity) -> { // CraftBukkit + if (entity instanceof EntityTameableAnimal) { + ((EntityTameableAnimal) entity).setOwnerUUID(this.uniqueID); + } + + entity.setPosition(this.locX(), this.locY() + 0.699999988079071D, this.locZ()); +- return ((WorldServer) this.world).addEntitySerialized(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit +- }).orElse(true); // CraftBukkit ++ boolean addedToWorld = ((WorldServer) this.world).addEntitySerialized(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit ++ return addedToWorld ? entity : null; ++ }).orElse(null); // CraftBukkit // Paper - false -> 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 105d0388998d1e35e634d2163fe1a44aa7037ac8..8661f97ac885daca068057c1fcc4eed54c6d7f14 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -493,6 +493,32 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + getHandle().getCooldownTracker().setCooldown(CraftMagicNumbers.getItem(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 discoverRecipes(Arrays.asList(recipe)) != 0; diff --git a/patches/server-unmapped/0001/0155-Profile-Lookup-Events.patch b/patches/server-unmapped/0001/0155-Profile-Lookup-Events.patch new file mode 100644 index 0000000000..a9a4210f19 --- /dev/null +++ b/patches/server-unmapped/0001/0155-Profile-Lookup-Events.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 17 Jun 2017 17:00:32 -0400 +Subject: [PATCH] Profile Lookup Events + +Adds a Pre Lookup Event and a Post Lookup Event so that plugins may prefill in profile data, and cache the responses from +profiles that had to be looked up. + +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java +index 582c169c85ac66f1f9430f79042e4655f776c157..08fdb681a68e8be6e4062af0630957ce3e524806 100644 +--- a/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java +@@ -1,11 +1,16 @@ + package com.destroystokyo.paper.profile; + ++import com.destroystokyo.paper.event.profile.LookupProfileEvent; ++import com.destroystokyo.paper.event.profile.PreLookupProfileEvent; ++import com.google.common.collect.Sets; + import com.mojang.authlib.Agent; + import com.mojang.authlib.Environment; ++import com.mojang.authlib.GameProfile; + import com.mojang.authlib.ProfileLookupCallback; + import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; + import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; + ++import java.util.Set; + public class PaperGameProfileRepository extends YggdrasilGameProfileRepository { + public PaperGameProfileRepository(YggdrasilAuthenticationService authenticationService, Environment environment) { + super(authenticationService, environment); +@@ -13,6 +18,50 @@ public class PaperGameProfileRepository extends YggdrasilGameProfileRepository { + + @Override + public void findProfilesByNames(String[] names, Agent agent, ProfileLookupCallback callback) { +- super.findProfilesByNames(names, agent, callback); ++ Set unfoundNames = Sets.newHashSet(); ++ for (String name : names) { ++ PreLookupProfileEvent event = new PreLookupProfileEvent(name); ++ event.callEvent(); ++ if (event.getUUID() != null) { ++ // Plugin provided UUI, we can skip network call. ++ GameProfile gameprofile = new GameProfile(event.getUUID(), name); ++ // We might even have properties! ++ Set profileProperties = event.getProfileProperties(); ++ if (!profileProperties.isEmpty()) { ++ for (ProfileProperty property : profileProperties) { ++ gameprofile.getProperties().put(property.getName(), CraftPlayerProfile.asAuthlib(property)); ++ } ++ } ++ callback.onProfileLookupSucceeded(gameprofile); ++ } else { ++ unfoundNames.add(name); ++ } ++ } ++ ++ // Some things were not found.... Proceed to look up. ++ if (!unfoundNames.isEmpty()) { ++ String[] namesArr = unfoundNames.toArray(new String[unfoundNames.size()]); ++ super.findProfilesByNames(namesArr, agent, new PreProfileLookupCallback(callback)); ++ } ++ } ++ ++ private static class PreProfileLookupCallback implements ProfileLookupCallback { ++ private final ProfileLookupCallback callback; ++ ++ PreProfileLookupCallback(ProfileLookupCallback callback) { ++ this.callback = callback; ++ } ++ ++ @Override ++ public void onProfileLookupSucceeded(GameProfile gameProfile) { ++ PlayerProfile from = CraftPlayerProfile.asBukkitMirror(gameProfile); ++ new LookupProfileEvent(from).callEvent(); ++ callback.onProfileLookupSucceeded(gameProfile); ++ } ++ ++ @Override ++ public void onProfileLookupFailed(GameProfile gameProfile, Exception e) { ++ callback.onProfileLookupFailed(gameProfile, e); ++ } + } + } diff --git a/patches/server-unmapped/0001/0156-Block-player-logins-during-server-shutdown.patch b/patches/server-unmapped/0001/0156-Block-player-logins-during-server-shutdown.patch new file mode 100644 index 0000000000..3b4a49574b --- /dev/null +++ b/patches/server-unmapped/0001/0156-Block-player-logins-during-server-shutdown.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sun, 2 Jul 2017 21:35:56 -0500 +Subject: [PATCH] Block player logins during server shutdown + + +diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java +index 9f87503c58f64bbfa829faa58600d7d9e64aecf1..651b84b3bba316407a217c941be4c20074c5bb90 100644 +--- a/src/main/java/net/minecraft/server/network/LoginListener.java ++++ b/src/main/java/net/minecraft/server/network/LoginListener.java +@@ -68,6 +68,12 @@ public class LoginListener implements PacketLoginInListener { + } + + public void tick() { ++ // Paper start - Do not allow logins while the server is shutting down ++ if (!MinecraftServer.getServer().isRunning()) { ++ this.disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(org.spigotmc.SpigotConfig.restartMessage)[0]); ++ return; ++ } ++ // Paper end + if (this.g == LoginListener.EnumProtocolState.READY_TO_ACCEPT) { + this.c(); + } else if (this.g == LoginListener.EnumProtocolState.DELAY_ACCEPT) { diff --git a/patches/server-unmapped/0001/0157-Entity-fromMobSpawner.patch b/patches/server-unmapped/0001/0157-Entity-fromMobSpawner.patch new file mode 100644 index 0000000000..544c58ff8b --- /dev/null +++ b/patches/server-unmapped/0001/0157-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 c37e4f4350be3ff2e8e533ed3c4c9fd2c19a2f34..828f238e8f3b1a98310532e07c72161582c91c5b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -268,6 +268,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; + public long activatedTick = Integer.MIN_VALUE; ++ public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one + protected int numCollisions = 0; // Paper + public void inactiveTick() { } + // Spigot end +@@ -1665,6 +1666,10 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + if (this.origin != null) { + nbttagcompound.set("Paper.Origin", this.createList(origin.getX(), origin.getY(), origin.getZ())); + } ++ // Save entity's from mob spawner status ++ if (spawnedViaMobSpawner) { ++ nbttagcompound.setBoolean("Paper.FromMobSpawner", true); ++ } + // Paper end + return nbttagcompound; + } catch (Throwable throwable) { +@@ -1793,6 +1798,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + if (!originTag.isEmpty()) { + origin = new org.bukkit.Location(world.getWorld(), originTag.getDoubleAt(0), originTag.getDoubleAt(1), originTag.getDoubleAt(2)); + } ++ ++ spawnedViaMobSpawner = nbttagcompound.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/MobSpawnerAbstract.java b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +index 76c98d576d3e567ec4482b30219f5a9107cb9703..43fcc001bb9815b352cb74af10290b2a4ccaa540 100644 +--- a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java ++++ b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +@@ -160,6 +160,7 @@ public abstract class MobSpawnerAbstract { + } + // Spigot End + } ++ entity.spawnedViaMobSpawner = true; // Paper + // Spigot Start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, blockposition).isCancelled()) { + Entity vehicle = entity.getVehicle(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index a58626b1a0160983a738a45c8a1d411eb347e6a2..4c2a35fb33da19a15a220dc5e0c9fa3233d657fb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1100,5 +1100,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + Location origin = getHandle().origin; + return origin == null ? null : origin.clone(); + } ++ ++ @Override ++ public boolean fromMobSpawner() { ++ return getHandle().spawnedViaMobSpawner; ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0158-Improve-the-Saddle-API-for-Horses.patch b/patches/server-unmapped/0001/0158-Improve-the-Saddle-API-for-Horses.patch new file mode 100644 index 0000000000..5587fd6b92 --- /dev/null +++ b/patches/server-unmapped/0001/0158-Improve-the-Saddle-API-for-Horses.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 10 Dec 2016 16:24:06 -0500 +Subject: [PATCH] Improve the Saddle API for Horses + +Not all horses with Saddles have armor. This lets us break up the horses with saddles +and access their saddle state separately from an interface shared with Armor. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +index 00b97d9ee7a3dd622c87e8efa288795d34db8fc7..62ccef35e4b4238c50faf778fbf3ea9a494ca387 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +@@ -6,6 +6,7 @@ import net.minecraft.world.entity.animal.horse.EntityHorseAbstract; + import org.apache.commons.lang.Validate; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.inventory.CraftInventoryAbstractHorse; ++import org.bukkit.craftbukkit.inventory.CraftSaddledInventory; + import org.bukkit.entity.AbstractHorse; + import org.bukkit.entity.AnimalTamer; + import org.bukkit.entity.Horse; +@@ -99,6 +100,6 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac + + @Override + public AbstractHorseInventory getInventory() { +- return new CraftInventoryAbstractHorse(getHandle().inventoryChest); ++ return new CraftSaddledInventory(getHandle().inventoryChest); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryHorse.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryHorse.java +index c3599b29c0b32d6fcf18a4a0adfbe8454c37834d..0389d15f8e0b6bb68316eaed0cf91acab8951cc7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryHorse.java +@@ -4,7 +4,7 @@ import net.minecraft.world.IInventory; + import org.bukkit.inventory.HorseInventory; + import org.bukkit.inventory.ItemStack; + +-public class CraftInventoryHorse extends CraftInventoryAbstractHorse implements HorseInventory { ++public class CraftInventoryHorse extends CraftSaddledInventory implements HorseInventory { + + public CraftInventoryHorse(IInventory inventory) { + super(inventory); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSaddledInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSaddledInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8c2674ca1be1346ea84bcd7c9c5d6ea540802a5f +--- /dev/null ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSaddledInventory.java +@@ -0,0 +1,12 @@ ++package org.bukkit.craftbukkit.inventory; ++ ++import net.minecraft.world.IInventory; ++import org.bukkit.inventory.SaddledHorseInventory; ++ ++public class CraftSaddledInventory extends CraftInventoryAbstractHorse implements SaddledHorseInventory { ++ ++ public CraftSaddledInventory(IInventory inventory) { ++ super(inventory); ++ } ++ ++} diff --git a/patches/server-unmapped/0001/0159-Implement-ensureServerConversions-API.patch b/patches/server-unmapped/0001/0159-Implement-ensureServerConversions-API.patch new file mode 100644 index 0000000000..c77231c475 --- /dev/null +++ b/patches/server-unmapped/0001/0159-Implement-ensureServerConversions-API.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 May 2016 22:43:12 -0400 +Subject: [PATCH] Implement 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/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index 6d320bbe0c75cc0df504faacf0f7d24804b90d5f..6e748e57c4818e11ac6d4c693bc4f6e1e889f4f8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -341,5 +341,11 @@ public final class CraftItemFactory implements ItemFactory { + final net.minecraft.nbt.NBTTagCompound tag = CraftItemStack.asNMSCopy(item).getTag(); + return net.kyori.adventure.text.event.HoverEvent.showItem(op.apply(net.kyori.adventure.text.event.HoverEvent.ShowItem.of(item.getType().getKey(), item.getAmount(), io.papermc.paper.adventure.PaperAdventure.asBinaryTagHolder(tag)))); + } ++ ++ // Paper start ++ @Override ++ public ItemStack ensureServerConversions(ItemStack item) { ++ return CraftItemStack.asCraftMirror(CraftItemStack.asNMSCopy(item)); ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0160-Implement-getI18NDisplayName.patch b/patches/server-unmapped/0001/0160-Implement-getI18NDisplayName.patch new file mode 100644 index 0000000000..6dd63ca6ca --- /dev/null +++ b/patches/server-unmapped/0001/0160-Implement-getI18NDisplayName.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 May 2016 23:59:38 -0400 +Subject: [PATCH] Implement getI18NDisplayName + +Gets the Display name as seen in the Client. +Currently the server only supports the English language. To override this, +You must replace the language file embedded in the server jar. + +diff --git a/src/main/java/net/minecraft/locale/LocaleLanguage.java b/src/main/java/net/minecraft/locale/LocaleLanguage.java +index 9b8d5e7e4c86a699e26b1b4d0b82e88887a44054..5218214225b50ac4059ab704086a457318e93e00 100644 +--- a/src/main/java/net/minecraft/locale/LocaleLanguage.java ++++ b/src/main/java/net/minecraft/locale/LocaleLanguage.java +@@ -30,7 +30,7 @@ public abstract class LocaleLanguage { + + private static LocaleLanguage c() { + Builder builder = ImmutableMap.builder(); +- BiConsumer biconsumer = builder::put; ++ BiConsumer biconsumer = builder::put; // Paper - decompile fix + + try { + InputStream inputstream = LocaleLanguage.class.getResourceAsStream("/assets/minecraft/lang/en_us.json"); +@@ -87,10 +87,12 @@ public abstract class LocaleLanguage { + + } + ++ public static LocaleLanguage getInstance() { return a(); } // Paper - OBFHELPER + public static LocaleLanguage a() { + return LocaleLanguage.d; + } + ++ public String translateKey(String key) { return a(key); } // Paper - OBFHELPER + public abstract String a(String s); + + public abstract boolean b(String s); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index 6e748e57c4818e11ac6d4c693bc4f6e1e889f4f8..e6c818d32713d9fb0f02a46696bd8a5dabe2a3ae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -347,5 +347,18 @@ public final class CraftItemFactory implements ItemFactory { + public ItemStack ensureServerConversions(ItemStack item) { + return CraftItemStack.asCraftMirror(CraftItemStack.asNMSCopy(item)); + } ++ ++ @Override ++ public String getI18NDisplayName(ItemStack item) { ++ net.minecraft.world.item.ItemStack nms = null; ++ if (item instanceof CraftItemStack) { ++ nms = ((CraftItemStack) item).handle; ++ } ++ if (nms == null) { ++ nms = CraftItemStack.asNMSCopy(item); ++ } ++ ++ return nms != null ? net.minecraft.locale.LocaleLanguage.getInstance().translateKey(nms.getItem().getName()) : null; ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0161-ProfileWhitelistVerifyEvent.patch b/patches/server-unmapped/0001/0161-ProfileWhitelistVerifyEvent.patch new file mode 100644 index 0000000000..e2d62752d0 --- /dev/null +++ b/patches/server-unmapped/0001/0161-ProfileWhitelistVerifyEvent.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 3 Jul 2017 18:11:10 -0500 +Subject: [PATCH] ProfileWhitelistVerifyEvent + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index a892bcf08dddac90f01caec81229259e1070c3ea..ae877ea38a63ef8d0bd9855e9b9279475bb6c465 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -615,9 +615,9 @@ public abstract class PlayerList { + + // return chatmessage; + if (!gameprofilebanentry.hasExpired()) event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(chatmessage)); // Spigot // Paper - Adventure +- } else if (!this.isWhitelisted(gameprofile)) { +- chatmessage = new ChatMessage("multiplayer.disconnect.not_whitelisted"); +- event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, PaperAdventure.LEGACY_SECTION_UXRC.deserialize(org.spigotmc.SpigotConfig.whitelistMessage)); // Spigot // Paper - Adventure ++ } else if (!this.isWhitelisted(gameprofile, event)) { // Paper ++ //chatmessage = new ChatMessage("multiplayer.disconnect.not_whitelisted"); // Paper ++ //event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, org.spigotmc.SpigotConfig.whitelistMessage); // Spigot // Paper - moved to isWhitelisted + } else if (getIPBans().isBanned(socketaddress) && !getIPBans().get(socketaddress).hasExpired()) { + IpBanEntry ipbanentry = this.l.get(socketaddress); + +@@ -1009,9 +1009,25 @@ public abstract class PlayerList { + this.server.getCommandDispatcher().a(entityplayer); + } + ++ // Paper start + public boolean isWhitelisted(GameProfile gameprofile) { +- return !this.hasWhitelist || this.operators.d(gameprofile) || this.whitelist.d(gameprofile); ++ return isWhitelisted(gameprofile, null); + } ++ public boolean isWhitelisted(GameProfile gameprofile, org.bukkit.event.player.PlayerLoginEvent loginEvent) { ++ boolean isOp = this.operators.d(gameprofile); ++ boolean isWhitelisted = !this.hasWhitelist || isOp || this.whitelist.d(gameprofile); ++ final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event; ++ event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(MCUtil.toBukkit(gameprofile), this.hasWhitelist, isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage); ++ event.callEvent(); ++ if (!event.isWhitelisted()) { ++ if (loginEvent != null) { ++ loginEvent.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, PaperAdventure.LEGACY_SECTION_UXRC.deserialize(event.getKickMessage() == null ? org.spigotmc.SpigotConfig.whitelistMessage : event.getKickMessage())); ++ } ++ return false; ++ } ++ return true; ++ } ++ // Paper end + + public boolean isOp(GameProfile gameprofile) { + return this.operators.d(gameprofile) || this.server.a(gameprofile) && this.server.getSaveData().o() || this.v; diff --git a/patches/server-unmapped/0001/0162-Fix-this-stupid-bullshit.patch b/patches/server-unmapped/0001/0162-Fix-this-stupid-bullshit.patch new file mode 100644 index 0000000000..aafbbfcab5 --- /dev/null +++ b/patches/server-unmapped/0001/0162-Fix-this-stupid-bullshit.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: DemonWav +Date: Sun, 6 Aug 2017 17:17:53 -0500 +Subject: [PATCH] Fix this stupid bullshit + +Disable the 15 second sleep when the server jar hasn't been rebuilt within a period of time. + +modified in order to prevent merge conflicts when Spigot changes/disables the warning, +and to provide some level of hint without being disruptive. + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index fb49dd66e40e8376e9d6141b97350e16b5d0215e..7d7b9984b867461b0a1025e5ec21ff7798281b8f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -218,10 +218,12 @@ public class Main { + Calendar deadline = Calendar.getInstance(); + deadline.add(Calendar.DAY_OF_YEAR, -28); + if (buildDate.before(deadline.getTime())) { +- System.err.println("*** Error, this build is outdated ***"); ++ // Paper start - This is some stupid bullshit ++ System.err.println("*** Warning, you've not updated in a while! ***"); + System.err.println("*** Please download a new build as per instructions from https://papermc.io/downloads ***"); // Paper +- System.err.println("*** Server will start in 20 seconds ***"); +- Thread.sleep(TimeUnit.SECONDS.toMillis(20)); ++ //System.err.println("*** Server will start in 20 seconds ***"); ++ //Thread.sleep(TimeUnit.SECONDS.toMillis(20)); ++ // Paper End + } + } + diff --git a/patches/server-unmapped/0001/0163-Ocelot-despawns-should-honor-nametags-and-leash.patch b/patches/server-unmapped/0001/0163-Ocelot-despawns-should-honor-nametags-and-leash.patch new file mode 100644 index 0000000000..e460482610 --- /dev/null +++ b/patches/server-unmapped/0001/0163-Ocelot-despawns-should-honor-nametags-and-leash.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 31 Jul 2017 01:54:40 -0500 +Subject: [PATCH] Ocelot despawns should honor nametags and leash + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityOcelot.java b/src/main/java/net/minecraft/world/entity/animal/EntityOcelot.java +index b0296cef410aa5af42dcf89217dd8853f3800663..f3e9c73f28584bcccd6f82d8974eabe4b4a892fa 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityOcelot.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityOcelot.java +@@ -128,7 +128,7 @@ public class EntityOcelot extends EntityAnimal { + + @Override + public boolean isTypeNotPersistent(double d0) { +- return !this.isTrusting() /*&& this.ticksLived > 2400*/; // CraftBukkit ++ return !this.isTrusting() && !this.hasCustomName() && !this.isLeashed() /*&& this.ticksLived > 2400*/; // CraftBukkit // Paper - honor name and leash + } + + public static AttributeProvider.Builder eK() { diff --git a/patches/server-unmapped/0001/0164-Reset-spawner-timer-when-spawner-event-is-cancelled.patch b/patches/server-unmapped/0001/0164-Reset-spawner-timer-when-spawner-event-is-cancelled.patch new file mode 100644 index 0000000000..442ca85737 --- /dev/null +++ b/patches/server-unmapped/0001/0164-Reset-spawner-timer-when-spawner-event-is-cancelled.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 31 Jul 2017 01:45:19 -0500 +Subject: [PATCH] Reset spawner timer when spawner event is cancelled + + +diff --git a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +index 43fcc001bb9815b352cb74af10290b2a4ccaa540..883c724fbb86a84ee903b5e7127f14726fe4cf24 100644 +--- a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java ++++ b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +@@ -161,6 +161,7 @@ public abstract class MobSpawnerAbstract { + // Spigot End + } + entity.spawnedViaMobSpawner = true; // Paper ++ flag = true; // Paper + // Spigot Start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, blockposition).isCancelled()) { + Entity vehicle = entity.getVehicle(); +@@ -184,7 +185,7 @@ public abstract class MobSpawnerAbstract { + ((EntityInsentient) entity).doSpawnEffect(); + } + +- flag = true; ++ /*flag = true;*/ // Paper - moved up above cancellable event + } + } + } diff --git a/patches/server-unmapped/0001/0165-Fix-MC-117075-TE-Unload-Lag-Spike.patch b/patches/server-unmapped/0001/0165-Fix-MC-117075-TE-Unload-Lag-Spike.patch new file mode 100644 index 0000000000..9fb1e85eef --- /dev/null +++ b/patches/server-unmapped/0001/0165-Fix-MC-117075-TE-Unload-Lag-Spike.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mezz +Date: Wed, 9 Aug 2017 17:51:22 -0500 +Subject: [PATCH] Fix MC-117075: TE Unload Lag Spike + + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 7456f3032a2213f592515c306b601eda542dad37..aa125f27808f8cbe916f176a775e48a7a7ac49ad 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -726,7 +726,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + gameprofilerfiller.enter("blockEntities"); + timings.tileEntityTick.startTiming(); // Spigot + if (!this.tileEntityListUnload.isEmpty()) { +- this.tileEntityListTick.removeAll(this.tileEntityListUnload); ++ // Paper start - Use alternate implementation with faster contains ++ java.util.Set toRemove = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()); ++ toRemove.addAll(tileEntityListUnload); ++ this.tileEntityListTick.removeAll(toRemove); ++ // Paper end + //this.tileEntityList.removeAll(this.tileEntityListUnload); // Paper - remove unused list + this.tileEntityListUnload.clear(); + } diff --git a/patches/server-unmapped/0001/0166-Allow-specifying-a-custom-authentication-servers-dow.patch b/patches/server-unmapped/0001/0166-Allow-specifying-a-custom-authentication-servers-dow.patch new file mode 100644 index 0000000000..98260be689 --- /dev/null +++ b/patches/server-unmapped/0001/0166-Allow-specifying-a-custom-authentication-servers-dow.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Thu, 17 Aug 2017 16:08:20 -0700 +Subject: [PATCH] Allow specifying a custom "authentication servers down" kick + message + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 48319aaf1c525c6fb7bdee5c2f570a0d056d4eae..52954fc3bf932cfc9d5ce63e3d3cace351305790 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -1,5 +1,6 @@ + package com.destroystokyo.paper; + ++import com.google.common.base.Strings; + import com.google.common.base.Throwables; + + import java.io.File; +@@ -273,4 +274,9 @@ public class PaperConfig { + private static void suggestPlayersWhenNull() { + suggestPlayersWhenNullTabCompletions = getBoolean("settings.suggest-player-names-when-null-tab-completions", suggestPlayersWhenNullTabCompletions); + } ++ ++ public static String authenticationServersDownKickMessage = ""; // empty = use translatable message ++ private static void authenticationServersDownKickMessage() { ++ authenticationServersDownKickMessage = Strings.emptyToNull(getString("messages.kick.authentication-servers-down", authenticationServersDownKickMessage)); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java +index 651b84b3bba316407a217c941be4c20074c5bb90..5b96c69014dbfb8eb3e2ecf370ad69f2ffc31453 100644 +--- a/src/main/java/net/minecraft/server/network/LoginListener.java ++++ b/src/main/java/net/minecraft/server/network/LoginListener.java +@@ -276,6 +276,10 @@ public class LoginListener implements PacketLoginInListener { + LoginListener.this.i = LoginListener.this.a(gameprofile); + LoginListener.this.g = LoginListener.EnumProtocolState.READY_TO_ACCEPT; + } else { ++ // Paper start ++ if (com.destroystokyo.paper.PaperConfig.authenticationServersDownKickMessage != null) { ++ LoginListener.this.disconnect(new ChatComponentText(com.destroystokyo.paper.PaperConfig.authenticationServersDownKickMessage)); ++ } else // Paper end + LoginListener.this.disconnect(new ChatMessage("multiplayer.disconnect.authservers_down")); + LoginListener.LOGGER.error("Couldn't verify username because servers are unavailable"); + } diff --git a/patches/server-unmapped/0001/0167-LivingEntity-setKiller.patch b/patches/server-unmapped/0001/0167-LivingEntity-setKiller.patch new file mode 100644 index 0000000000..0673905f40 --- /dev/null +++ b/patches/server-unmapped/0001/0167-LivingEntity-setKiller.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 31 Jul 2017 01:49:48 -0500 +Subject: [PATCH] LivingEntity#setKiller + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index c17c0e1570d6ee2498d3f643df90a94e49ee2e3e..ff26ddce40d7ba74396360603a97bfab835b6202 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -180,7 +180,7 @@ public abstract class EntityLiving extends Entity { + public float aE; + @Nullable + public EntityHuman killer; +- protected int lastDamageByPlayerTime; ++ public int lastDamageByPlayerTime; // Paper - protected -> public + protected boolean killed; + protected int ticksFarFromPlayer; + protected float aJ; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index c654026587bc9bf77b39f59a0c89991ac581da1e..c43c300963bae9bca6ab9c9389dd53e42318715c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -8,6 +8,7 @@ import java.util.Iterator; + import java.util.List; + import java.util.Set; + import java.util.UUID; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.world.EnumHand; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.effect.MobEffect; +@@ -344,6 +345,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return getHandle().killer == null ? null : (Player) getHandle().killer.getBukkitEntity(); + } + ++ // Paper start ++ @Override ++ public void setKiller(Player killer) { ++ EntityPlayer entityPlayer = killer == null ? null : ((CraftPlayer) killer).getHandle(); ++ getHandle().killer = entityPlayer; ++ getHandle().lastDamager = entityPlayer; ++ getHandle().lastDamageByPlayerTime = entityPlayer == null ? 0 : 100; // 100 value taken from EntityLiving#damageEntity ++ } ++ // Paper end ++ + @Override + public boolean addPotionEffect(PotionEffect effect) { + return addPotionEffect(effect, false); diff --git a/patches/server-unmapped/0001/0168-Handle-plugin-prefixes-using-Log4J-configuration.patch b/patches/server-unmapped/0001/0168-Handle-plugin-prefixes-using-Log4J-configuration.patch new file mode 100644 index 0000000000..66d9ef47f0 --- /dev/null +++ b/patches/server-unmapped/0001/0168-Handle-plugin-prefixes-using-Log4J-configuration.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Thu, 21 Sep 2017 16:14:55 +0200 +Subject: [PATCH] Handle plugin prefixes using Log4J configuration + +Display logger name in the console for all loggers except the +root logger, Bukkit's logger ("Minecraft") and Minecraft loggers. +Since plugins now use the plugin name as logger name this will +restore the plugin prefixes without having to prepend them manually +to the log messages. + +Logger prefixes are shown by default for all loggers except for +the root logger, the Minecraft/Mojang loggers and the Bukkit loggers. +This may cause additional prefixes to be disabled for plugins bypassing +the plugin logger. + +diff --git a/pom.xml b/pom.xml +index e0fcbe1b13a5ec1244d6d904ca76336a47967c9b..70a7778ae19941bb78b8669b1429cb3d525f58a4 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -76,7 +76,7 @@ + + org.apache.logging.log4j + log4j-core +- runtime ++ compile + + + org.apache.logging.log4j +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index 3c93a497a790b8d800852db2ac48feca41f45cef..e8e5e5b568ba53dd006f1461cb4f027ceeae5528 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -290,7 +290,7 @@ public class SpigotConfig + private static void playerSample() + { + playerSample = getInt( "settings.sample-count", 12 ); +- System.out.println( "Server Ping Player Sample Count: " + playerSample ); ++ Bukkit.getLogger().log( Level.INFO, "Server Ping Player Sample Count: {0}", playerSample ); // Paper - Use logger + } + + public static int playerShuffle; +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 620b9490e5f159080e50289d127404a1b56adbef..a8bdaaeaa1a9316848416f0533739b9b083ca151 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -5,10 +5,22 @@ + + + +- ++ ++ ++ ++ ++ ++ + + +- ++ ++ ++ ++ ++ ++ + + + diff --git a/patches/server-unmapped/0001/0169-Include-Log4J2-SLF4J-implementation.patch b/patches/server-unmapped/0001/0169-Include-Log4J2-SLF4J-implementation.patch new file mode 100644 index 0000000000..395d0aac73 --- /dev/null +++ b/patches/server-unmapped/0001/0169-Include-Log4J2-SLF4J-implementation.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Thu, 21 Sep 2017 16:33:35 +0200 +Subject: [PATCH] Include Log4J2 SLF4J implementation + + +diff --git a/pom.xml b/pom.xml +index 70a7778ae19941bb78b8669b1429cb3d525f58a4..3ebdf97dd88e9461331434da4a8cd02bd25148ca 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -83,6 +83,11 @@ + log4j-api + compile + ++ ++ org.apache.logging.log4j ++ log4j-slf4j-impl ++ runtime ++ + + org.apache.logging.log4j + log4j-iostreams diff --git a/patches/server-unmapped/0001/0170-Improve-Log4J-Configuration-Plugin-Loggers.patch b/patches/server-unmapped/0001/0170-Improve-Log4J-Configuration-Plugin-Loggers.patch new file mode 100644 index 0000000000..5afb9562c0 --- /dev/null +++ b/patches/server-unmapped/0001/0170-Improve-Log4J-Configuration-Plugin-Loggers.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Sat, 23 Sep 2017 21:07:20 +0200 +Subject: [PATCH] Improve Log4J Configuration / Plugin Loggers + +Add full exceptions to log4j to not truncate stack traces + +Disable logger prefix for various plugins bypassing the plugin logger + +Some plugins bypass the plugin logger and add the plugin prefix +manually to the log message. Since they use other logger names +(e.g. qualified class names) these would now also appear in the +log. Disable the logger prefix for these plugins so the messages +show up correctly. + +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index a8bdaaeaa1a9316848416f0533739b9b083ca151..476f4a5cbe664ddd05474cb88553018bd334a5b8 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -6,19 +6,21 @@ + + + +- ++ + +- ++ ++ + + + + + +- ++ + +- ++ ++ + + + diff --git a/patches/server-unmapped/0001/0171-Add-PlayerJumpEvent.patch b/patches/server-unmapped/0001/0171-Add-PlayerJumpEvent.patch new file mode 100644 index 0000000000..61478af206 --- /dev/null +++ b/patches/server-unmapped/0001/0171-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/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index bf3195eaf1fc7bdddafbb54d0ccf825e95b63210..7af07ca8e1fa20361c95841b48dd70cdf46a18b5 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1168,7 +1168,34 @@ public class PlayerConnection implements PacketListenerPlayIn { + boolean flag = d8 > 0.0D; + + if (this.player.isOnGround() && !packetplayinflying.b() && flag) { +- this.player.jump(); ++ // Paper start - Add player jump event ++ Player player = this.getPlayer(); ++ 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 (packetplayinflying.hasPos) { ++ to.setX(packetplayinflying.x); ++ to.setY(packetplayinflying.y); ++ to.setZ(packetplayinflying.z); ++ } ++ ++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. ++ if (packetplayinflying.hasLook) { ++ to.setYaw(packetplayinflying.yaw); ++ to.setPitch(packetplayinflying.pitch); ++ } ++ ++ com.destroystokyo.paper.event.player.PlayerJumpEvent event = new com.destroystokyo.paper.event.player.PlayerJumpEvent(player, from, to); ++ ++ if (event.callEvent()) { ++ this.player.jump(); ++ } else { ++ from = event.getFrom(); ++ this.internalTeleport(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch(), Collections.emptySet()); ++ return; ++ } ++ // Paper end + } + + this.player.move(EnumMoveType.PLAYER, new Vec3D(d7, d8, d9)); diff --git a/patches/server-unmapped/0001/0172-handle-PacketPlayInKeepAlive-async.patch b/patches/server-unmapped/0001/0172-handle-PacketPlayInKeepAlive-async.patch new file mode 100644 index 0000000000..1d3d75d1c5 --- /dev/null +++ b/patches/server-unmapped/0001/0172-handle-PacketPlayInKeepAlive-async.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 5 Oct 2017 01:54:07 +0100 +Subject: [PATCH] handle PacketPlayInKeepAlive async + +In 1.12.2, Mojang moved the processing of PacketPlayInKeepAlive off the main +thread, while entirely correct for the server, this causes issues with +plugins which are expecting the PlayerQuitEvent on the main thread. + +In order to counteract some bad behavior, we will post handling of the +disconnection to the main thread, but leave the actual processing of the packet +off the main thread. + +also adding some additional logging in order to help work out what is causing +random disconnections for clients. + +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 7af07ca8e1fa20361c95841b48dd70cdf46a18b5..685415240d7be66fe968198b33c1865a906e18c9 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -2778,14 +2778,18 @@ public class PlayerConnection implements PacketListenerPlayIn { + + @Override + public void a(PacketPlayInKeepAlive packetplayinkeepalive) { +- PlayerConnectionUtils.ensureMainThread(packetplayinkeepalive, this, this.player.getWorldServer()); // CraftBukkit ++ //PlayerConnectionUtils.ensureMainThread(packetplayinkeepalive, this, this.player.getWorldServer()); // CraftBukkit // Paper - This shouldn't be on the main thread + if (this.awaitingKeepAlive && packetplayinkeepalive.b() == this.h) { + int i = (int) (SystemUtils.getMonotonicMillis() - this.lastKeepAlive); + + this.player.ping = (this.player.ping * 3 + i) / 4; + this.awaitingKeepAlive = false; + } else if (!this.isExemptPlayer()) { ++ // Paper start - This needs to be handled on the main thread for plugins ++ minecraftServer.scheduleOnMain(() -> { + this.disconnect(new ChatMessage("disconnect.timeout")); ++ }); ++ // Paper end + } + + } diff --git a/patches/server-unmapped/0001/0173-Expose-client-protocol-version-and-virtual-host.patch b/patches/server-unmapped/0001/0173-Expose-client-protocol-version-and-virtual-host.patch new file mode 100644 index 0000000000..3f282910be --- /dev/null +++ b/patches/server-unmapped/0001/0173-Expose-client-protocol-version-and-virtual-host.patch @@ -0,0 +1,129 @@ +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..8a716c1647aa29906be26ac262e93ebd2c1adfaa +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java +@@ -0,0 +1,50 @@ ++package com.destroystokyo.paper.network; ++ ++import net.minecraft.network.NetworkManager; ++ ++import java.net.InetSocketAddress; ++ ++import javax.annotation.Nullable; ++ ++public class PaperNetworkClient implements NetworkClient { ++ ++ private final NetworkManager networkManager; ++ ++ PaperNetworkClient(NetworkManager networkManager) { ++ this.networkManager = networkManager; ++ } ++ ++ @Override ++ public InetSocketAddress getAddress() { ++ return (InetSocketAddress) this.networkManager.getSocketAddress(); ++ } ++ ++ @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/NetworkManager.java b/src/main/java/net/minecraft/network/NetworkManager.java +index f093b465b868e6003bb2b5ee634a624b5b054493..60e4a4aa3854aaeb250d1318f2f25cf3591ea1d3 100644 +--- a/src/main/java/net/minecraft/network/NetworkManager.java ++++ b/src/main/java/net/minecraft/network/NetworkManager.java +@@ -70,6 +70,10 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + private float s; + private int t; + private boolean u; ++ // Paper start - NetworkClient implementation ++ public int protocolVersion; ++ public java.net.InetSocketAddress virtualHost; ++ // Paper end + + public NetworkManager(EnumProtocolDirection enumprotocoldirection) { + this.h = enumprotocoldirection; +diff --git a/src/main/java/net/minecraft/network/protocol/handshake/PacketHandshakingInSetProtocol.java b/src/main/java/net/minecraft/network/protocol/handshake/PacketHandshakingInSetProtocol.java +index b290ddfbc19aed3e44169281c3dae5429dac0062..14c002376540d2039fc2fe2ef746e53471a9cb08 100644 +--- a/src/main/java/net/minecraft/network/protocol/handshake/PacketHandshakingInSetProtocol.java ++++ b/src/main/java/net/minecraft/network/protocol/handshake/PacketHandshakingInSetProtocol.java +@@ -39,6 +39,7 @@ public class PacketHandshakingInSetProtocol implements Packet +Date: Sun, 15 Oct 2017 00:29:07 +0100 +Subject: [PATCH] revert serverside behavior of keepalives + +This patch intends to bump up the time that a client has to reply to the +server back to 30 seconds as per pre 1.12.2, which allowed clients +more than enough time to reply potentially allowing them to be less +tempermental due to lag spikes on the network thread, e.g. that caused +by plugins that are interacting with netty. + +We also add a system property to allow people to tweak how long the server +will wait for a reply. There is a compromise here between lower and higher +values, lower values will mean that dead connections can be closed sooner, +whereas higher values will make this less sensitive to issues such as spikes +from networking or during connections flood of chunk packets on slower clients, + at the cost of dead connections being kept open for longer. + +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 685415240d7be66fe968198b33c1865a906e18c9..3628965d2a18a367c2357b54b65786fb90c38205 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -223,7 +223,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + private final MinecraftServer minecraftServer; + public EntityPlayer player; + private int e; +- private long lastKeepAlive; private void setLastPing(long lastPing) { this.lastKeepAlive = lastPing;}; private long getLastPing() { return this.lastKeepAlive;}; // Paper - OBFHELPER ++ private long lastKeepAlive = SystemUtils.getMonotonicMillis(); private void setLastPing(long lastPing) { this.lastKeepAlive = lastPing;}; private long getLastPing() { return this.lastKeepAlive;}; // Paper - OBFHELPER + private boolean awaitingKeepAlive; private void setPendingPing(boolean isPending) { this.awaitingKeepAlive = isPending;}; private boolean isPendingPing() { return this.awaitingKeepAlive;}; // Paper - OBFHELPER + private long h; private void setKeepAliveID(long keepAliveID) { this.h = keepAliveID;}; private long getKeepAliveID() {return this.h; }; // Paper - OBFHELPER + // CraftBukkit start - multithreaded fields +@@ -254,6 +254,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + private int E; + private int receivedMovePackets; + private int processedMovePackets; ++ private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit + + public PlayerConnection(MinecraftServer minecraftserver, NetworkManager networkmanager, EntityPlayer entityplayer) { + this.minecraftServer = minecraftserver; +@@ -340,18 +341,25 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + + this.minecraftServer.getMethodProfiler().enter("keepAlive"); +- long i = SystemUtils.getMonotonicMillis(); +- +- if (i - this.lastKeepAlive >= 25000L) { // CraftBukkit +- if (this.awaitingKeepAlive) { +- this.disconnect(new ChatMessage("disconnect.timeout")); +- } else { +- this.awaitingKeepAlive = true; +- this.lastKeepAlive = i; +- this.h = i; +- this.sendPacket(new PacketPlayOutKeepAlive(this.h)); ++ // Paper Start - give clients a longer time to respond to pings as per pre 1.12.2 timings ++ // This should effectively place the keepalive handling back to "as it was" before 1.12.2 ++ long currentTime = SystemUtils.getMonotonicMillis(); ++ long elapsedTime = currentTime - this.getLastPing(); ++ ++ if (this.isPendingPing()) { ++ if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected ++ PlayerConnection.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getName()); // more info ++ this.disconnect(new ChatMessage("disconnect.timeout", new Object[0])); ++ } ++ } else { ++ if (elapsedTime >= 15000L) { // 15 seconds ++ this.setPendingPing(true); ++ this.setLastPing(currentTime); ++ this.setKeepAliveID(currentTime); ++ this.sendPacket(new PacketPlayOutKeepAlive(this.getKeepAliveID())); + } + } ++ // Paper end + + this.minecraftServer.getMethodProfiler().exit(); + // CraftBukkit start diff --git a/patches/server-unmapped/0001/0175-Send-attack-SoundEffects-only-to-players-who-can-see.patch b/patches/server-unmapped/0001/0175-Send-attack-SoundEffects-only-to-players-who-can-see.patch new file mode 100644 index 0000000000..5c3d2d05c3 --- /dev/null +++ b/patches/server-unmapped/0001/0175-Send-attack-SoundEffects-only-to-players-who-can-see.patch @@ -0,0 +1,80 @@ +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/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 63e8062ae3f3407b92b72b5fccaa958c39282fb8..f9d0623a3ed5f49758cd5e97fe9f63a5b3198e58 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -28,6 +28,7 @@ import net.minecraft.network.chat.ChatMessage; + import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.network.chat.IChatMutableComponent; + import net.minecraft.network.protocol.game.PacketPlayOutEntityVelocity; ++import net.minecraft.network.protocol.game.PacketPlayOutNamedSoundEffect; + import net.minecraft.network.syncher.DataWatcher; + import net.minecraft.network.syncher.DataWatcherObject; + import net.minecraft.network.syncher.DataWatcherRegistry; +@@ -1126,7 +1127,7 @@ public abstract class EntityHuman extends EntityLiving { + int i = b0 + EnchantmentManager.b((EntityLiving) this); + + if (this.isSprinting() && flag) { +- this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_KNOCKBACK, this.getSoundCategory(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_KNOCKBACK, this.getSoundCategory(), 1.0F, 1.0F); // Paper - send while respecting visibility + ++i; + flag1 = true; + } +@@ -1201,7 +1202,7 @@ public abstract class EntityHuman extends EntityLiving { + } + } + +- this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_SWEEP, this.getSoundCategory(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_SWEEP, this.getSoundCategory(), 1.0F, 1.0F); // Paper - send while respecting visibility + this.ex(); + } + +@@ -1229,15 +1230,15 @@ public abstract class EntityHuman extends EntityLiving { + } + + if (flag2) { +- this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_CRIT, this.getSoundCategory(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_CRIT, this.getSoundCategory(), 1.0F, 1.0F); // Paper - send while respecting visibility + this.a(entity); + } + + if (!flag2 && !flag3) { + if (flag) { +- this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_STRONG, this.getSoundCategory(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_STRONG, this.getSoundCategory(), 1.0F, 1.0F); // Paper - send while respecting visibility + } else { +- this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_WEAK, this.getSoundCategory(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_WEAK, this.getSoundCategory(), 1.0F, 1.0F); // Paper - send while respecting visibility + } + } + +@@ -1289,7 +1290,7 @@ public abstract class EntityHuman extends EntityLiving { + + this.applyExhaustion(world.spigotConfig.combatExhaustion, EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value + } else { +- this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_NODAMAGE, this.getSoundCategory(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_PLAYER_ATTACK_NODAMAGE, this.getSoundCategory(), 1.0F, 1.0F); // Paper - send while respecting visibility + if (flag4) { + entity.extinguish(); + } +@@ -1724,6 +1725,14 @@ public abstract class EntityHuman extends EntityLiving { + public int getExpToLevel() { + return this.expLevel >= 30 ? 112 + (this.expLevel - 30) * 9 : (this.expLevel >= 15 ? 37 + (this.expLevel - 15) * 5 : 7 + this.expLevel * 2); + } ++ // Paper start - send SoundEffect to everyone who can see fromEntity ++ private static void sendSoundEffect(EntityHuman fromEntity, double x, double y, double z, SoundEffect soundEffect, SoundCategory soundCategory, float volume, float pitch) { ++ fromEntity.world.playSound(fromEntity, x, y, z, soundEffect, soundCategory, volume, pitch); // This will not send the effect to the entity himself ++ if (fromEntity instanceof EntityPlayer) { ++ ((EntityPlayer) fromEntity).playerConnection.sendPacket(new PacketPlayOutNamedSoundEffect(soundEffect, soundCategory, x, y, z, volume, pitch)); ++ } ++ } ++ // Paper end + + // CraftBukkit start + public void applyExhaustion(float f) { diff --git a/patches/server-unmapped/0001/0176-Option-for-maximum-exp-value-when-merging-orbs.patch b/patches/server-unmapped/0001/0176-Option-for-maximum-exp-value-when-merging-orbs.patch new file mode 100644 index 0000000000..d13725e270 --- /dev/null +++ b/patches/server-unmapped/0001/0176-Option-for-maximum-exp-value-when-merging-orbs.patch @@ -0,0 +1,61 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 90ca51dfdbb3045dd528450225cba96f5834166e..6c692e58cde22003ecbf6dc5695799147c39905a 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -342,4 +342,10 @@ public class PaperWorldConfig { + disableCreeperLingeringEffect = getBoolean("disable-creeper-lingering-effect", false); + log("Creeper lingering effect: " + disableCreeperLingeringEffect); + } ++ ++ public int expMergeMaxValue; ++ private void expMergeMaxValue() { ++ expMergeMaxValue = getInt("experience-merge-max-value", -1); ++ log("Experience Merge Max Value: " + expMergeMaxValue); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index aa5c991fbcfef2d7ef9537321ae2e7d072184644..f25dfec4ca51f430de46105f2d1baf2340b96e31 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -578,16 +578,32 @@ public class CraftEventFactory { + EntityExperienceOrb xp = (EntityExperienceOrb) entity; + 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.expMergeMaxValue; ++ final boolean mergeUnconditionally = world.paperConfig.expMergeMaxValue <= 0; ++ if (mergeUnconditionally || xp.value < maxValue) { // Paper - Skip iteration if unnecessary ++ + List entities = world.getEntities(entity, entity.getBoundingBox().grow(radius, radius, radius)); + for (Entity e : entities) { + if (e instanceof EntityExperienceOrb) { + EntityExperienceOrb loopItem = (EntityExperienceOrb) e; +- if (!loopItem.dead) { +- xp.value += loopItem.value; +- loopItem.die(); ++ // Paper start ++ if (!loopItem.dead && !(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.die(); ++ } ++ // Paper end + } + } + } ++ ++ } // Paper end - End iteration skip check - All tweaking ends here + } + // Spigot end + } else if (!(entity instanceof EntityPlayer)) { diff --git a/patches/server-unmapped/0001/0177-Add-PlayerArmorChangeEvent.patch b/patches/server-unmapped/0001/0177-Add-PlayerArmorChangeEvent.patch new file mode 100644 index 0000000000..5adb2dda30 --- /dev/null +++ b/patches/server-unmapped/0001/0177-Add-PlayerArmorChangeEvent.patch @@ -0,0 +1,43 @@ +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/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index ff26ddce40d7ba74396360603a97bfab835b6202..8a43a85a728c15dbc0fdd2fc8dc5dfff9a589358 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.entity; + ++import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; // Paper + import com.google.common.base.Objects; + import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableMap; +@@ -2646,6 +2647,13 @@ public abstract class EntityLiving extends Entity { + ItemStack itemstack1 = this.getEquipment(enumitemslot); + + if (!ItemStack.matches(itemstack1, itemstack)) { ++ // Paper start - PlayerArmorChangeEvent ++ if (this instanceof EntityPlayer && enumitemslot.getType() == EnumItemSlot.Function.ARMOR) { ++ final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(itemstack); ++ final org.bukkit.inventory.ItemStack newItem = CraftItemStack.asBukkitCopy(itemstack1); ++ new PlayerArmorChangeEvent((Player) this.getBukkitEntity(), PlayerArmorChangeEvent.SlotType.valueOf(enumitemslot.name()), oldItem, newItem).callEvent(); ++ } ++ // Paper end + if (map == null) { + map = Maps.newEnumMap(EnumItemSlot.class); + } +diff --git a/src/main/java/net/minecraft/world/entity/EnumItemSlot.java b/src/main/java/net/minecraft/world/entity/EnumItemSlot.java +index 8e7673c6072c3f8ddcebd7a719304ea41d809a36..59ad7d8dc1c8ee00d142dc6063c3416ccdce4ff8 100644 +--- a/src/main/java/net/minecraft/world/entity/EnumItemSlot.java ++++ b/src/main/java/net/minecraft/world/entity/EnumItemSlot.java +@@ -16,6 +16,7 @@ public enum EnumItemSlot { + this.j = s; + } + ++ public EnumItemSlot.Function getType() { return this.a(); } // Paper - OBFHELPER + public EnumItemSlot.Function a() { + return this.g; + } diff --git a/patches/server-unmapped/0001/0178-Prevent-logins-from-being-processed-when-the-player-.patch b/patches/server-unmapped/0001/0178-Prevent-logins-from-being-processed-when-the-player-.patch new file mode 100644 index 0000000000..e619c605b1 --- /dev/null +++ b/patches/server-unmapped/0001/0178-Prevent-logins-from-being-processed-when-the-player-.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: killme +Date: Sun, 12 Nov 2017 19:40:01 +0100 +Subject: [PATCH] Prevent logins from being processed when the player has + disconnected + + +diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java +index 5b96c69014dbfb8eb3e2ecf370ad69f2ffc31453..714f44a668eb35b3c61bb9ab140f884917efd6f5 100644 +--- a/src/main/java/net/minecraft/server/network/LoginListener.java ++++ b/src/main/java/net/minecraft/server/network/LoginListener.java +@@ -75,7 +75,11 @@ public class LoginListener implements PacketLoginInListener { + } + // Paper end + if (this.g == LoginListener.EnumProtocolState.READY_TO_ACCEPT) { +- this.c(); ++ // Paper start - prevent logins to be processed even though disconnect was called ++ if (networkManager.isConnected()) { ++ this.c(); ++ } ++ // Paper end + } else if (this.g == LoginListener.EnumProtocolState.DELAY_ACCEPT) { + EntityPlayer entityplayer = this.server.getPlayerList().getPlayer(this.i.getId()); + diff --git a/patches/server-unmapped/0001/0179-use-CB-BlockState-implementations-for-captured-block.patch b/patches/server-unmapped/0001/0179-use-CB-BlockState-implementations-for-captured-block.patch new file mode 100644 index 0000000000..98f6c1e8cc --- /dev/null +++ b/patches/server-unmapped/0001/0179-use-CB-BlockState-implementations-for-captured-block.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 16 Nov 2017 12:12:41 +0000 +Subject: [PATCH] use CB BlockState implementations for captured blocks + +When modifying the world, CB will store a copy of the affected +blocks in order to restore their state in the case that the event +is cancelled. This change only modifies the collection of blocks +in the world by normal means, e.g. not during tree population, +as the potentially marginal overheads would serve no advantage. + +CB was using a CraftBlockState for all blocks, which causes issues +should any block that uses information beyond a data ID would suffer +from missing information, e.g. Skulls. + +By using CBs CraftBlock#getState(), we will maintain a proper copy of +the blockstate that will be valid for restoration, as opposed to dropping +information on restoration when the event is cancelled. + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index aa125f27808f8cbe916f176a775e48a7a7ac49ad..118acde8ec2cb613e03b891b037cc8bb06548682 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -124,7 +124,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + public boolean captureBlockStates = false; + public boolean captureTreeGeneration = false; +- public Map capturedBlockStates = new java.util.LinkedHashMap<>(); ++ public Map capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper + public Map capturedTileEntities = new HashMap<>(); + public List captureDrops; + public long ticksPerAnimalSpawns; +@@ -346,7 +346,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public boolean a(BlockPosition blockposition, IBlockData iblockdata, int i, int j) { + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { +- CapturedBlockState blockstate = capturedBlockStates.get(blockposition); ++ CraftBlockState blockstate = capturedBlockStates.get(blockposition); + if (blockstate == null) { + blockstate = CapturedBlockState.getTreeBlockState(this, blockposition, i); + this.capturedBlockStates.put(blockposition.immutableCopy(), blockstate); +@@ -366,7 +366,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + // CraftBukkit start - capture blockstates + boolean captured = false; + if (this.captureBlockStates && !this.capturedBlockStates.containsKey(blockposition)) { +- CapturedBlockState blockstate = CapturedBlockState.getBlockState(this, blockposition, i); ++ CraftBlockState blockstate = (CraftBlockState) world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()).getState(); // Paper - use CB getState to get a suitable snapshot ++ blockstate.setFlag(i); // Paper - set flag + this.capturedBlockStates.put(blockposition.immutableCopy(), blockstate); + captured = true; + } +@@ -624,7 +625,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public IBlockData getType(BlockPosition blockposition) { + // CraftBukkit start - tree generation + if (captureTreeGeneration) { +- CapturedBlockState previous = capturedBlockStates.get(blockposition); ++ CraftBlockState previous = capturedBlockStates.get(blockposition); // Paper + if (previous != null) { + return previous.getHandle(); + } diff --git a/patches/server-unmapped/0001/0180-API-to-get-a-BlockState-without-a-snapshot.patch b/patches/server-unmapped/0001/0180-API-to-get-a-BlockState-without-a-snapshot.patch new file mode 100644 index 0000000000..5fe5db1a81 --- /dev/null +++ b/patches/server-unmapped/0001/0180-API-to-get-a-BlockState-without-a-snapshot.patch @@ -0,0 +1,147 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 6 Nov 2017 21:08:22 -0500 +Subject: [PATCH] API to get a BlockState without a snapshot + +This allows you to get a BlockState without creating a snapshot, operating +on the real tile entity. + +This is useful for where performance is needed + +also Avoid NPE during CraftBlockEntityState load if could not get TE + +If Tile Entity was null, correct Sign to return empty lines instead of null + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +index 9b44ca96669ce423e5649f11743226dfdd9ce746..48daa039ffa8ccb7b6f3ca47bdc56394addf9254 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +@@ -47,6 +47,7 @@ public abstract class TileEntity implements net.minecraft.server.KeyedObject { / + public TileEntity(TileEntityTypes tileentitytypes) { + this.position = BlockPosition.ZERO; + this.tileType = tileentitytypes; ++ persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY); // Paper - always init + } + + // Paper start +@@ -95,7 +96,7 @@ public abstract class TileEntity implements net.minecraft.server.KeyedObject { / + public void load(IBlockData iblockdata, NBTTagCompound nbttagcompound) { + this.position = new BlockPosition(nbttagcompound.getInt("x"), nbttagcompound.getInt("y"), nbttagcompound.getInt("z")); + // CraftBukkit start - read container +- this.persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY); ++ this.persistentDataContainer.clear(); // Paper - clear instead of reinit + + net.minecraft.nbt.NBTBase persistentDataTag = nbttagcompound.get("PublicBukkitValues"); + if (persistentDataTag instanceof NBTTagCompound) { +@@ -245,7 +246,12 @@ public abstract class TileEntity implements net.minecraft.server.KeyedObject { / + } + + // CraftBukkit start - add method ++ // Paper start + public InventoryHolder getOwner() { ++ return getOwner(true); ++ } ++ public InventoryHolder getOwner(boolean useSnapshot) { ++ // Paper end + if (world == null) return null; + // Spigot start + org.bukkit.block.Block block = world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()); +@@ -254,7 +260,7 @@ public abstract class TileEntity implements net.minecraft.server.KeyedObject { / + return null; + } + // Spigot end +- org.bukkit.block.BlockState state = block.getState(); ++ org.bukkit.block.BlockState state = block.getState(useSnapshot); // Paper + if (state instanceof InventoryHolder) return (InventoryHolder) state; + return null; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 255aac0803b436434bc00822f3698c4f3ba7e0ac..8a6d8f21937ce7e2ac4623a3083421ed5ef9aa63 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -316,6 +316,20 @@ public class CraftBlock implements Block { + + @Override + public BlockState getState() { ++ // Paper start - allow disabling the use of snapshots ++ return getState(true); ++ } ++ public BlockState getState(boolean useSnapshot) { ++ boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT; ++ CraftBlockEntityState.DISABLE_SNAPSHOT = !useSnapshot; ++ try { ++ return getState0(); ++ } finally { ++ CraftBlockEntityState.DISABLE_SNAPSHOT = prev; ++ } ++ } ++ public BlockState getState0() { ++ // Paper end + Material material = getType(); + + switch (material) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index dcf3f9265b0b00a7bbb9ff428e10da3c198ba08a..2f0b48869077c27d0cacea81a99c9e34ff59c684 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -26,20 +26,40 @@ public class CraftBlockEntityState extends CraftBlockState + this.tileEntity = tileEntityClass.cast(world.getHandle().getTileEntity(this.getPosition())); + Preconditions.checkState(this.tileEntity != null, "Tile is null, asynchronous access? %s", block); + ++ // Paper start ++ this.snapshotDisabled = DISABLE_SNAPSHOT; ++ if (DISABLE_SNAPSHOT) { ++ this.snapshot = this.tileEntity; ++ } else { ++ this.snapshot = this.createSnapshot(this.tileEntity); ++ } + // copy tile entity data: +- this.snapshot = this.createSnapshot(tileEntity); +- this.load(snapshot); ++ if(this.snapshot != null) { ++ this.load(this.snapshot); ++ } ++ // Paper end + } + ++ public final boolean snapshotDisabled; // Paper ++ public static boolean DISABLE_SNAPSHOT = false; // Paper ++ + public CraftBlockEntityState(Material material, T tileEntity) { + super(material); + + this.tileEntityClass = (Class) tileEntity.getClass(); + this.tileEntity = tileEntity; +- ++ // Paper start ++ this.snapshotDisabled = DISABLE_SNAPSHOT; ++ if (DISABLE_SNAPSHOT) { ++ this.snapshot = this.tileEntity; ++ } else { ++ this.snapshot = this.createSnapshot(this.tileEntity); ++ } + // copy tile entity data: +- this.snapshot = this.createSnapshot(tileEntity); +- this.load(snapshot); ++ if(this.snapshot != null) { ++ this.load(this.snapshot); ++ } ++ // Paper end + } + + private T createSnapshot(T tileEntity) { +diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java +index f342feee4e2274cdc51fef6caace52cc31eefb18..a4c888236bf09a25f234831a041ca5a4a2c972ef 100644 +--- a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java +@@ -155,4 +155,10 @@ public final class CraftPersistentDataContainer implements PersistentDataContain + public Map serialize() { + return (Map) CraftNBTTagConfigSerializer.serialize(toTagCompound()); + } ++ ++ // Paper start ++ public void clear() { ++ this.customDataTags.clear(); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0181-AsyncTabCompleteEvent.patch b/patches/server-unmapped/0001/0181-AsyncTabCompleteEvent.patch new file mode 100644 index 0000000000..cc3f304c43 --- /dev/null +++ b/patches/server-unmapped/0001/0181-AsyncTabCompleteEvent.patch @@ -0,0 +1,130 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +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 + +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 3628965d2a18a367c2357b54b65786fb90c38205..fc624315b156f450c1cbc87a81e9eeff5d31b4c2 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -713,10 +713,10 @@ public class PlayerConnection implements PacketListenerPlayIn { + + @Override + public void a(PacketPlayInTabComplete packetplayintabcomplete) { +- PlayerConnectionUtils.ensureMainThread(packetplayintabcomplete, this, this.player.getWorldServer()); ++ // PlayerConnectionUtils.ensureMainThread(packetplayintabcomplete, this, this.player.getWorldServer()); // Paper - run this async + // CraftBukkit start + if (chatSpamField.addAndGet(this, 1) > 500 && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) { +- this.disconnect(new ChatMessage("disconnect.spam", new Object[0])); ++ minecraftServer.scheduleOnMain(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0]))); // Paper + return; + } + // CraftBukkit end +@@ -726,12 +726,35 @@ public class PlayerConnection implements PacketListenerPlayIn { + stringreader.skip(); + } + +- ParseResults parseresults = this.minecraftServer.getCommandDispatcher().a().parse(stringreader, this.player.getCommandListener()); ++ // Paper start - async tab completion ++ com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event; ++ java.util.List completions = new java.util.ArrayList<>(); ++ String buffer = packetplayintabcomplete.c(); ++ event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getPlayer(), completions, ++ buffer, true, null); ++ event.callEvent(); ++ completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.getCompletions(); ++ // 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()) { + +- this.minecraftServer.getCommandDispatcher().a().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.networkManager.sendPacket(new PacketPlayOutTabComplete(packetplayintabcomplete.b(), suggestions)); +- }); ++ this.minecraftServer.scheduleOnMain(() -> { // Paper - This needs to be on main ++ ParseResults parseresults = this.minecraftServer.getCommandDispatcher().a().parse(stringreader, this.player.getCommandListener()); ++ ++ this.minecraftServer.getCommandDispatcher().a().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.networkManager.sendPacket(new PacketPlayOutTabComplete(packetplayintabcomplete.b(), suggestions)); ++ }); ++ }); ++ } ++ } else if (!completions.isEmpty()) { ++ com.mojang.brigadier.suggestion.SuggestionsBuilder builder = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packetplayintabcomplete.c(), stringreader.getTotalLength()); ++ ++ builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + 1); ++ completions.forEach(builder::suggest); ++ player.playerConnection.sendPacket(new PacketPlayOutTabComplete(packetplayintabcomplete.b(), builder.buildFuture().join())); ++ } ++ // Paper end - async tab completion + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 7d2fc05ddb18369aed29595e3c0dcbf6db136309..f1d4e2ac2823e2246463350b21f28c6d32f4f2c5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1844,7 +1844,7 @@ public final class CraftServer implements Server { + offers = tabCompleteChat(player, message); + } + +- TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers); ++ TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers, message.startsWith("/") || forceCommand, pos != null ? net.minecraft.server.MCUtil.toLocation(((CraftWorld) player.getWorld()).getHandle(), new BlockPosition(pos)) : null); // Paper + 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 a957695457cf3252848ce6ef37069692841b8e28..c5e00bd9e2790992202aadf8eec2002fc88c78f1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -28,6 +28,39 @@ 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 ++ com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event; ++ java.util.List completions = new java.util.ArrayList<>(); ++ event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(server.getConsoleSender(), completions, ++ buffer, true, null); ++ event.callEvent(); ++ completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.getCompletions(); ++ ++ 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 = 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); ++ return syncEvent.callEvent() ? syncEvent.getCompletions() : com.google.common.collect.ImmutableList.of(); ++ } ++ }; ++ server.getServer().processQueue.add(syncCompletions); ++ try { ++ completions = syncCompletions.get(); ++ } catch (InterruptedException | ExecutionException e1) { ++ e1.printStackTrace(); ++ } ++ } ++ ++ if (!completions.isEmpty()) { ++ candidates.addAll(completions.stream().map(Candidate::new).collect(java.util.stream.Collectors.toList())); ++ } ++ return; ++ } ++ + // Paper end + Waitable> waitable = new Waitable>() { + @Override diff --git a/patches/server-unmapped/0001/0182-Avoid-NPE-in-PathfinderGoalTempt.patch b/patches/server-unmapped/0001/0182-Avoid-NPE-in-PathfinderGoalTempt.patch new file mode 100644 index 0000000000..22279d4515 --- /dev/null +++ b/patches/server-unmapped/0001/0182-Avoid-NPE-in-PathfinderGoalTempt.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 29 Nov 2017 22:18:54 -0500 +Subject: [PATCH] Avoid NPE in PathfinderGoalTempt + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalTempt.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalTempt.java +index 54b345b5d2fc8d3c511533281f6d387f046f8000..9a120ab9c5c6c858bf3d1690196fe657e76cc1b7 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalTempt.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalTempt.java +@@ -65,7 +65,7 @@ public class PathfinderGoalTempt extends PathfinderGoal { + } + this.target = (event.getTarget() == null) ? null : ((CraftLivingEntity) event.getTarget()).getHandle(); + } +- return tempt; ++ return tempt && this.target != null; // Paper - must have target - plugin might of cancelled + // CraftBukkit end + } + } diff --git a/patches/server-unmapped/0001/0183-PlayerPickupExperienceEvent.patch b/patches/server-unmapped/0001/0183-PlayerPickupExperienceEvent.patch new file mode 100644 index 0000000000..994fed3ea2 --- /dev/null +++ b/patches/server-unmapped/0001/0183-PlayerPickupExperienceEvent.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 19 Dec 2017 22:02:53 -0500 +Subject: [PATCH] PlayerPickupExperienceEvent + +Allows plugins to cancel a player picking up an experience orb + +diff --git a/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java b/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java +index f4da22b33c704e675510b4b1a3aa7c180088be29..e3dfb018b06c0139594ddbb88fab2ca8d43ab12f 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java +@@ -5,6 +5,7 @@ import net.minecraft.core.BlockPosition; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityExperienceOrb; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.sounds.SoundEffects; + import net.minecraft.tags.Tag; + import net.minecraft.tags.TagsFluid; +@@ -232,7 +233,7 @@ public class EntityExperienceOrb extends Entity { + @Override + public void pickup(EntityHuman entityhuman) { + if (!this.world.isClientSide) { +- if (this.d == 0 && entityhuman.bu == 0) { ++ if (this.d == 0 && entityhuman.bu == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(((EntityPlayer) entityhuman).getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper + entityhuman.bu = 2; + entityhuman.receive(this, 1); + Entry entry = EnchantmentManager.a(Enchantments.MENDING, (EntityLiving) entityhuman, ItemStack::f); diff --git a/patches/server-unmapped/0001/0184-ExperienceOrbMergeEvent.patch b/patches/server-unmapped/0001/0184-ExperienceOrbMergeEvent.patch new file mode 100644 index 0000000000..9ee861bba7 --- /dev/null +++ b/patches/server-unmapped/0001/0184-ExperienceOrbMergeEvent.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 19 Dec 2017 22:57:26 -0500 +Subject: [PATCH] ExperienceOrbMergeEvent + +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 f25dfec4ca51f430de46105f2d1baf2340b96e31..791a4490c25f88aa4525c98e794ff3b2bfe194ed 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -588,7 +588,7 @@ public class CraftEventFactory { + if (e instanceof EntityExperienceOrb) { + EntityExperienceOrb loopItem = (EntityExperienceOrb) e; + // Paper start +- if (!loopItem.dead && !(maxValue > 0 && loopItem.value >= maxValue)) { ++ if (!loopItem.dead && !(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 + 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-unmapped/0001/0185-Ability-to-apply-mending-to-XP-API.patch b/patches/server-unmapped/0001/0185-Ability-to-apply-mending-to-XP-API.patch new file mode 100644 index 0000000000..f8fd95c016 --- /dev/null +++ b/patches/server-unmapped/0001/0185-Ability-to-apply-mending-to-XP-API.patch @@ -0,0 +1,104 @@ +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. + +diff --git a/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java b/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java +index e3dfb018b06c0139594ddbb88fab2ca8d43ab12f..3387a19044b3ee2a1ef549c328c8bc354a5b6d23 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java +@@ -265,10 +265,12 @@ public class EntityExperienceOrb extends Entity { + } + } + ++ public final int durToXp(int i) { return b(i); } // Paper OBFHELPER + private int b(int i) { + return i / 2; + } + ++ public final int xpToDur(int i) { return c(i); } // Paper OBFHELPER + private int c(int i) { + return i * 2; + } +diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentManager.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentManager.java +index d313b02f41e4f4a90676cbb37afce4e92dd4d664..72afbf8f537770540e90a2880ea81de137ea10f5 100644 +--- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentManager.java ++++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentManager.java +@@ -269,8 +269,8 @@ public class EnchantmentManager { + return getEnchantmentLevel(Enchantments.CHANNELING, itemstack) > 0; + } + +- @Nullable +- public static Entry b(Enchantment enchantment, EntityLiving entityliving) { ++ public static @javax.annotation.Nonnull ItemStack getRandomEquippedItemWithEnchant(Enchantment enchantment, EntityLiving entityliving) { Entry entry = b(enchantment, entityliving); return entry != null ? entry.getValue() : ItemStack.NULL_ITEM; } // Paper - OBFHELPER ++ @Nullable public static Entry b(Enchantment enchantment, EntityLiving entityliving) { + return a(enchantment, entityliving, (itemstack) -> { + 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 cf9ae60db30ef09bb4c89935a42632e562b6d61e..4766a78a0562e5ae6e7d4850bd7b5d71425c3a0c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -58,13 +58,17 @@ import net.minecraft.server.level.WorldServer; + import net.minecraft.server.network.PlayerConnection; + import net.minecraft.server.players.WhiteListEntry; + import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityExperienceOrb; + import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EntityTypes; + import net.minecraft.world.entity.ai.attributes.AttributeMapBase; + import net.minecraft.world.entity.ai.attributes.AttributeModifiable; + import net.minecraft.world.entity.ai.attributes.GenericAttributes; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.inventory.Container; + import net.minecraft.world.item.EnumColor; ++import net.minecraft.world.item.enchantment.EnchantmentManager; ++import net.minecraft.world.item.enchantment.Enchantments; + import net.minecraft.world.level.EnumGamemode; + import net.minecraft.world.level.block.entity.TileEntitySign; + import net.minecraft.world.level.saveddata.maps.MapIcon; +@@ -1180,8 +1184,37 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return GameMode.getByValue(getHandle().playerInteractManager.getGameMode().getId()); + } + ++ // Paper start + @Override +- public void giveExp(int exp) { ++ public int applyMending(int amount) { ++ EntityPlayer handle = getHandle(); ++ // Logic copied from EntityExperienceOrb and remapped to unobfuscated methods/properties ++ net.minecraft.world.item.ItemStack itemstack = EnchantmentManager.getRandomEquippedItemWithEnchant(Enchantments.MENDING, handle); ++ if (!itemstack.isEmpty() && itemstack.getItem().usesDurability()) { ++ ++ EntityExperienceOrb orb = EntityTypes.EXPERIENCE_ORB.create(handle.world); ++ orb.value = amount; ++ orb.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM; ++ orb.setPositionRaw(handle.locX(), handle.locY(), handle.locZ()); ++ ++ int i = Math.min(orb.xpToDur(amount), itemstack.getDamage()); ++ org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, i); ++ i = event.getRepairAmount(); ++ orb.dead = true; ++ if (!event.isCancelled()) { ++ amount -= orb.durToXp(i); ++ itemstack.setDamage(itemstack.getDamage() - i); ++ } ++ } ++ return amount; ++ } ++ ++ @Override ++ public void giveExp(int exp, boolean applyMending) { ++ if (applyMending) { ++ exp = this.applyMending(exp); ++ } ++ // Paper end + getHandle().giveExp(exp); + } + diff --git a/patches/server-unmapped/0001/0186-Make-max-squid-spawn-height-configurable.patch b/patches/server-unmapped/0001/0186-Make-max-squid-spawn-height-configurable.patch new file mode 100644 index 0000000000..d6706377d7 --- /dev/null +++ b/patches/server-unmapped/0001/0186-Make-max-squid-spawn-height-configurable.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 11 Jan 2018 16:47:28 -0600 +Subject: [PATCH] Make max squid spawn height configurable + +I don't know why upstream made only the minimum height configurable but +whatever + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 6c692e58cde22003ecbf6dc5695799147c39905a..3c39f1bb3d88baaaed4dd43c51faeef89bb5c6c2 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -348,4 +348,9 @@ public class PaperWorldConfig { + expMergeMaxValue = getInt("experience-merge-max-value", -1); + log("Experience Merge Max Value: " + expMergeMaxValue); + } ++ ++ public double squidMaxSpawnHeight; ++ private void squidMaxSpawnHeight() { ++ squidMaxSpawnHeight = getDouble("squid-spawn-height.maximum", 0.0D); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntitySquid.java b/src/main/java/net/minecraft/world/entity/animal/EntitySquid.java +index 3cc9af925aaf116140d5f36cfc56aa001ffb4e35..7ce5e2597b34d3a4d2a79d73c15e893c064fc88c 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntitySquid.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntitySquid.java +@@ -196,7 +196,8 @@ public class EntitySquid extends EntityWaterAnimal { + } + + public static boolean b(EntityTypes entitytypes, GeneratorAccess generatoraccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) { +- return blockposition.getY() > generatoraccess.getMinecraftWorld().spigotConfig.squidSpawnRangeMin && blockposition.getY() < generatoraccess.getSeaLevel(); // Spigot ++ final double maxHeight = generatoraccess.getMinecraftWorld().paperConfig.squidMaxSpawnHeight > 0 ? generatoraccess.getMinecraftWorld().paperConfig.squidMaxSpawnHeight : generatoraccess.getSeaLevel(); // Paper ++ return blockposition.getY() > generatoraccess.getMinecraftWorld().spigotConfig.squidSpawnRangeMin && blockposition.getY() < maxHeight; // Spigot // Paper + } + + public void a(float f, float f1, float f2) { diff --git a/patches/server-unmapped/0001/0187-PreCreatureSpawnEvent.patch b/patches/server-unmapped/0001/0187-PreCreatureSpawnEvent.patch new file mode 100644 index 0000000000..6c66c1146f --- /dev/null +++ b/patches/server-unmapped/0001/0187-PreCreatureSpawnEvent.patch @@ -0,0 +1,200 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 14 Jan 2018 17:01:31 -0500 +Subject: [PATCH] PreCreatureSpawnEvent + +Adds an event to fire before an Entity is created, so that plugins that need to cancel +CreatureSpawnEvent can do so from this event instead. + +Cancelling CreatureSpawnEvent rapidly causes a lot of garbage collection and CPU waste +as it's done after the Entity object has been fully created. + +Mob Limiting plugins and blanket "ban this type of monster" plugins should use this event +instead and save a lot of server resources. + +See: https://github.com/PaperMC/Paper/issues/917 + +diff --git a/src/main/java/net/minecraft/world/entity/EntityTypes.java b/src/main/java/net/minecraft/world/entity/EntityTypes.java +index a707ba365e25ea15e2e9d22110696b6136aa0c6f..1355c074353611669c947cb0f06c67be0ab418aa 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityTypes.java ++++ b/src/main/java/net/minecraft/world/entity/EntityTypes.java +@@ -17,6 +17,7 @@ import net.minecraft.nbt.NBTTagList; + import net.minecraft.network.chat.ChatMessage; + import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.resources.MinecraftKey; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.WorldServer; + import net.minecraft.tags.Tag; +@@ -317,6 +318,20 @@ public class EntityTypes { + + @Nullable + public T spawnCreature(WorldServer worldserver, @Nullable NBTTagCompound nbttagcompound, @Nullable IChatBaseComponent ichatbasecomponent, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start - Call PreCreatureSpawnEvent ++ org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(EntityTypes.getName(this).getKey()); ++ if (type != null) { ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; ++ event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ MCUtil.toLocation(worldserver, blockposition), ++ type, ++ spawnReason ++ ); ++ if (!event.callEvent()) { ++ return null; ++ } ++ } ++ // Paper end + T t0 = this.createCreature(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1); + + if (t0 != null) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorGolemLastSeen.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorGolemLastSeen.java +index 41f1aecbf6b506231a1b3b525fe0ce23b35c7840..6c01e460d3a1ff7f865ebc34dfd28d55b16aab98 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorGolemLastSeen.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorGolemLastSeen.java +@@ -33,7 +33,7 @@ public class SensorGolemLastSeen extends Sensor { + Optional> optional = entityliving.getBehaviorController().getMemory(MemoryModuleType.MOBS); + + if (optional.isPresent()) { +- boolean flag = ((List) optional.get()).stream().anyMatch((entityliving1) -> { ++ boolean flag = optional.get().stream().anyMatch((entityliving1) -> { // Paper - decompile fixes + return entityliving1.getEntityType().equals(EntityTypes.IRON_GOLEM); + }); + +@@ -44,6 +44,7 @@ public class SensorGolemLastSeen extends Sensor { + } + } + ++ public static void setDetectedRecently(EntityLiving entityLiving) { b(entityLiving); } // Paper - OBFHELPER + public static void b(EntityLiving entityliving) { + entityliving.getBehaviorController().a(MemoryModuleType.GOLEM_DETECTED_RECENTLY, true, 600L); + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +index adce6f17a5dd33004f8a67cd55d195de029e0263..534efe39beee393d11705b8f0b13ce4ca727c3eb 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +@@ -30,6 +30,7 @@ import net.minecraft.network.protocol.game.PacketDebug; + import net.minecraft.network.syncher.DataWatcher; + import net.minecraft.network.syncher.DataWatcherObject; + import net.minecraft.network.syncher.DataWatcherRegistry; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.WorldServer; + import net.minecraft.sounds.SoundEffect; +@@ -936,6 +937,21 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + BlockPosition blockposition1 = this.a(blockposition, d0, d1); + + if (blockposition1 != null) { ++ // Paper start - Call PreCreatureSpawnEvent ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; ++ event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ MCUtil.toLocation(world, blockposition1), ++ org.bukkit.entity.EntityType.IRON_GOLEM, ++ org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE ++ ); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ SensorGolemLastSeen.b(this); // Set Golem Last Seen to stop it from spawning another one ++ return null; ++ } ++ break; ++ } ++ // Paper end + EntityIronGolem entityirongolem = (EntityIronGolem) EntityTypes.IRON_GOLEM.createCreature(worldserver, (NBTTagCompound) null, (IChatBaseComponent) null, (EntityHuman) null, blockposition1, EnumMobSpawn.MOB_SUMMONED, false, false); + + if (entityirongolem != null) { +diff --git a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +index 883c724fbb86a84ee903b5e7127f14726fe4cf24..d4b8126f12fdf7d9b4f882d3ed7d8da544ed9e8a 100644 +--- a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java ++++ b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +@@ -12,6 +12,7 @@ import net.minecraft.core.particles.Particles; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTTagList; + import net.minecraft.resources.MinecraftKey; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.WorldServer; + import net.minecraft.util.UtilColor; + import net.minecraft.util.WeightedRandom; +@@ -125,6 +126,27 @@ public abstract class MobSpawnerAbstract { + WorldServer worldserver = (WorldServer) world; + + if (EntityPositionTypes.a((EntityTypes) optional.get(), worldserver, EnumMobSpawn.SPAWNER, new BlockPosition(d3, d4, d5), world.getRandom())) { ++ // Paper start ++ EntityTypes entityType = optional.get(); ++ String key = EntityTypes.getName(entityType).getKey(); ++ ++ org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(key); ++ if (type != null) { ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; ++ event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ MCUtil.toLocation(world, d3, d4, d5), ++ type, ++ org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER ++ ); ++ if (!event.callEvent()) { ++ flag = true; ++ if (event.shouldAbortSpawn()) { ++ break; ++ } ++ continue; ++ } ++ } ++ // Paper end + Entity entity = EntityTypes.a(nbttagcompound, world, (entity1) -> { + entity1.setPositionRotation(d3, d4, d5, entity1.yaw, entity1.pitch); + return entity1; +diff --git a/src/main/java/net/minecraft/world/level/SpawnerCreature.java b/src/main/java/net/minecraft/world/level/SpawnerCreature.java +index fd0595fd584046326eccacdf0a6afe40c5e84eed..1969d1002b3182338614a2be0519fcdc385b7a44 100644 +--- a/src/main/java/net/minecraft/world/level/SpawnerCreature.java ++++ b/src/main/java/net/minecraft/world/level/SpawnerCreature.java +@@ -15,6 +15,7 @@ import net.minecraft.core.EnumDirection; + import net.minecraft.core.IPosition; + import net.minecraft.core.IRegistry; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.WorldServer; + import net.minecraft.tags.Tag; + import net.minecraft.tags.TagsBlock; +@@ -216,9 +217,16 @@ public final class SpawnerCreature { + j1 = biomesettingsmobs_c.d + worldserver.random.nextInt(1 + biomesettingsmobs_c.e - biomesettingsmobs_c.d); + } + +- if (a(worldserver, enumcreaturetype, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2) && spawnercreature_c.test(biomesettingsmobs_c.c, blockposition_mutableblockposition, ichunkaccess)) { ++ // Paper start ++ Boolean doSpawning = a(worldserver, enumcreaturetype, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); ++ if (doSpawning == null) { ++ return; ++ } ++ if (doSpawning && spawnercreature_c.test(biomesettingsmobs_c.c, blockposition_mutableblockposition, ichunkaccess)) { ++ // Paper end + EntityInsentient entityinsentient = a(worldserver, biomesettingsmobs_c.c); + ++ + if (entityinsentient == null) { + return; + } +@@ -271,8 +279,24 @@ public final class SpawnerCreature { + } + } + +- private static boolean a(WorldServer worldserver, EnumCreatureType enumcreaturetype, StructureManager structuremanager, ChunkGenerator chunkgenerator, BiomeSettingsMobs.c biomesettingsmobs_c, BlockPosition.MutableBlockPosition blockposition_mutableblockposition, double d0) { ++ private static Boolean a(WorldServer worldserver, EnumCreatureType enumcreaturetype, StructureManager structuremanager, ChunkGenerator chunkgenerator, BiomeSettingsMobs.c biomesettingsmobs_c, BlockPosition.MutableBlockPosition blockposition_mutableblockposition, double d0) { // Paper + EntityTypes entitytypes = biomesettingsmobs_c.c; ++ // Paper start ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; ++ org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(EntityTypes.getName(entitytypes).getKey()); ++ if (type != null) { ++ event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ MCUtil.toLocation(worldserver, blockposition_mutableblockposition), ++ type, SpawnReason.NATURAL ++ ); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ return null; ++ } ++ return false; ++ } ++ } ++ // Paper end + + if (entitytypes.e() == EnumCreatureType.MISC) { + return false; diff --git a/patches/server-unmapped/0001/0188-PlayerNaturallySpawnCreaturesEvent.patch b/patches/server-unmapped/0001/0188-PlayerNaturallySpawnCreaturesEvent.patch new file mode 100644 index 0000000000..86f95a52c1 --- /dev/null +++ b/patches/server-unmapped/0001/0188-PlayerNaturallySpawnCreaturesEvent.patch @@ -0,0 +1,79 @@ +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/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index cfb98fac55f38522dd4dcf78869db28984a6b8c6..bd8f58b398ea5afe3d4785865a40e22eaf266926 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -612,6 +612,15 @@ public class ChunkProviderServer extends IChunkProvider { + this.world.getMethodProfiler().exit(); + //List list = Lists.newArrayList(this.playerChunkMap.f()); // Paper + //Collections.shuffle(list); // Paper ++ //Paper start - call player naturally spawn event ++ int chunkRange = world.spigotConfig.mobSpawnRange; ++ chunkRange = (chunkRange > world.spigotConfig.viewDistance) ? (byte) world.spigotConfig.viewDistance : chunkRange; ++ chunkRange = Math.min(chunkRange, 8); ++ for (EntityPlayer entityPlayer : this.world.getPlayers()) { ++ entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); ++ entityPlayer.playerNaturallySpawnedEvent.callEvent(); ++ }; ++ // Paper end + this.playerChunkMap.f().forEach((playerchunk) -> { // Paper - no... just no... + Optional optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 09385eabefeb7d59de1ce4138648badd123396f9..3d517ab98da5fd56101e97b5678f7180839269f8 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.level; + ++import com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent; + import com.google.common.collect.Lists; + import com.mojang.authlib.GameProfile; + import com.mojang.datafixers.util.Either; +@@ -227,6 +228,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public boolean sentListPacket = false; + public Integer clientViewDistance; + // CraftBukkit end ++ public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper + + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 56f83a930c3dad1a1de366bff530131d92b4893c..c6b9b02e6d31bebb3f8c0cadd68e4b5c47fab090 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -958,12 +958,23 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + chunkRange = (chunkRange > world.spigotConfig.viewDistance) ? (byte) world.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 + // Spigot end + long i = chunkcoordintpair.pair(); + + return !this.chunkDistanceManager.d(i) ? true : this.playerMap.a(i).noneMatch((entityplayer) -> { +- return !entityplayer.isSpectator() && a(chunkcoordintpair, (Entity) entityplayer) < blockRange; // Spigot ++ // Paper start - ++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; ++ double blockRange = 16384.0D; ++ if (reducedRange) { ++ event = entityplayer.playerNaturallySpawnedEvent; ++ if (event == null || event.isCancelled()) return false; ++ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); ++ } ++ ++ return (!entityplayer.isSpectator() && a(chunkcoordintpair, (Entity) entityplayer) < blockRange); // Spigot ++ // Paper end + }); + } + diff --git a/patches/server-unmapped/0001/0189-Add-setPlayerProfile-API-for-Skulls.patch b/patches/server-unmapped/0001/0189-Add-setPlayerProfile-API-for-Skulls.patch new file mode 100644 index 0000000000..da897bd972 --- /dev/null +++ b/patches/server-unmapped/0001/0189-Add-setPlayerProfile-API-for-Skulls.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 19 Jan 2018 00:36:25 -0500 +Subject: [PATCH] Add setPlayerProfile API for Skulls + +This allows you to create already filled textures on Skulls to avoid texture lookups +which commonly cause rate limit issues with Mojang API + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java +index 80ee7ab69ff70431d51321d403e5e3400a24bd67..00fb749f4d181d8d830496cf741d589e10af5098 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSkull.java +@@ -1,5 +1,7 @@ + package org.bukkit.craftbukkit.block; + ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; + import com.google.common.base.Preconditions; + import com.mojang.authlib.GameProfile; + import net.minecraft.server.MinecraftServer; +@@ -15,6 +17,7 @@ import org.bukkit.block.data.BlockData; + import org.bukkit.block.data.Directional; + import org.bukkit.block.data.Rotatable; + import org.bukkit.craftbukkit.entity.CraftPlayer; ++import javax.annotation.Nullable; + + public class CraftSkull extends CraftBlockEntityState implements Skull { + +@@ -105,6 +108,20 @@ public class CraftSkull extends CraftBlockEntityState implement + } + } + ++ // Paper start ++ @Override ++ public void setPlayerProfile(PlayerProfile profile) { ++ Preconditions.checkNotNull(profile, "profile"); ++ this.profile = CraftPlayerProfile.asAuthlibCopy(profile); ++ } ++ ++ @Nullable ++ @Override ++ public PlayerProfile getPlayerProfile() { ++ return profile != null ? CraftPlayerProfile.asBukkitCopy(profile) : null; ++ } ++ // Paper end ++ + @Override + public BlockFace getRotation() { + BlockData blockData = getBlockData(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +index 8298ae9bf1c5635f08552c15f004b3d0f6e9f19b..6db0d35eba647a0e81ca464fa52dc4a404ddd2ab 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +@@ -8,6 +8,8 @@ import net.minecraft.nbt.GameProfileSerializer; + import net.minecraft.nbt.NBTBase; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.world.level.block.entity.TileEntitySkull; ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; + import org.bukkit.Bukkit; + import org.bukkit.Material; + import org.bukkit.OfflinePlayer; +@@ -18,6 +20,7 @@ import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; + import org.bukkit.craftbukkit.util.CraftMagicNumbers; + import org.bukkit.inventory.meta.SkullMeta; + ++import javax.annotation.Nullable; + @DelegateDeserialization(SerializableMeta.class) + class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + +@@ -149,6 +152,19 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + return hasOwner() ? profile.getName() : null; + } + ++ // Paper start ++ @Override ++ public void setPlayerProfile(@Nullable PlayerProfile profile) { ++ setProfile((profile == null) ? null : CraftPlayerProfile.asAuthlibCopy(profile)); ++ } ++ ++ @Nullable ++ @Override ++ public PlayerProfile getPlayerProfile() { ++ return profile != null ? CraftPlayerProfile.asBukkitCopy(profile) : null; ++ } ++ // Paper end ++ + @Override + public OfflinePlayer getOwningPlayer() { + if (hasOwner()) { +@@ -175,7 +191,7 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + } else { + // Paper start - Use Online Players Skull + GameProfile newProfile = null; +- net.minecraft.server.EntityPlayer player = net.minecraft.server.MinecraftServer.getServer().getPlayerList().getPlayer(name); ++ net.minecraft.server.level.EntityPlayer player = net.minecraft.server.MinecraftServer.getServer().getPlayerList().getPlayer(name); + if (player != null) newProfile = player.getProfile(); + if (newProfile == null) newProfile = new GameProfile(null, name); + setProfile(newProfile); diff --git a/patches/server-unmapped/0001/0190-Fill-Profile-Property-Events.patch b/patches/server-unmapped/0001/0190-Fill-Profile-Property-Events.patch new file mode 100644 index 0000000000..30643d081c --- /dev/null +++ b/patches/server-unmapped/0001/0190-Fill-Profile-Property-Events.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 2 Jan 2018 00:31:26 -0500 +Subject: [PATCH] Fill Profile Property Events + +Allows plugins to populate profile properties from local sources to avoid calls out to Mojang API +to fill in textures for example. + +If Mojang API does need to be hit, event fire so you can get the results. + +This is useful for implementing a ProfileCache for Player Skulls + +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +index 93d73c27340645c7502acafdc0b2cfbc1a759dd8..5c7d2ee19243d0911a3a00af3ae42078a2ccba94 100644 +--- a/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +@@ -1,6 +1,8 @@ + package com.destroystokyo.paper.profile; + + import com.mojang.authlib.Environment; ++import com.destroystokyo.paper.event.profile.FillProfileEvent; ++import com.destroystokyo.paper.event.profile.PreFillProfileEvent; + import com.mojang.authlib.GameProfile; + import com.mojang.authlib.minecraft.MinecraftProfileTexture; + import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +@@ -20,7 +22,15 @@ public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionServi + + @Override + public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) { +- return super.fillProfileProperties(profile, requireSecure); ++ CraftPlayerProfile playerProfile = (CraftPlayerProfile) CraftPlayerProfile.asBukkitMirror(profile); ++ new PreFillProfileEvent(playerProfile).callEvent(); ++ profile = playerProfile.getGameProfile(); ++ if (profile.isComplete() && profile.getProperties().containsKey("textures")) { ++ return profile; ++ } ++ GameProfile gameProfile = super.fillProfileProperties(profile, requireSecure); ++ new FillProfileEvent(CraftPlayerProfile.asBukkitMirror(gameProfile)).callEvent(); ++ return gameProfile; + } + + @Override diff --git a/patches/server-unmapped/0001/0191-PlayerAdvancementCriterionGrantEvent.patch b/patches/server-unmapped/0001/0191-PlayerAdvancementCriterionGrantEvent.patch new file mode 100644 index 0000000000..e919ba3032 --- /dev/null +++ b/patches/server-unmapped/0001/0191-PlayerAdvancementCriterionGrantEvent.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 19 Jan 2018 08:15:29 -0600 +Subject: [PATCH] PlayerAdvancementCriterionGrantEvent + + +diff --git a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java +index d832cc2f3cc1ee73299a7f07eb2ccc3d392d5cf8..7d37626277823d5db05189c20bb1ebf91aa2a286 100644 +--- a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java ++++ b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java +@@ -297,6 +297,12 @@ public class AdvancementDataPlayer { + boolean flag1 = advancementprogress.isDone(); + + if (advancementprogress.a(s)) { ++ // Paper start ++ if (!new com.destroystokyo.paper.event.player.PlayerAdvancementCriterionGrantEvent(this.player.getBukkitEntity(), advancement.bukkit, s).callEvent()) { ++ advancementprogress.b(s); ++ return false; ++ } ++ // Paper end + this.d(advancement); + this.j.add(advancement); + flag = true; diff --git a/patches/server-unmapped/0001/0192-Add-ArmorStand-Item-Meta.patch b/patches/server-unmapped/0001/0192-Add-ArmorStand-Item-Meta.patch new file mode 100644 index 0000000000..658b0384d9 --- /dev/null +++ b/patches/server-unmapped/0001/0192-Add-ArmorStand-Item-Meta.patch @@ -0,0 +1,296 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 27 Jan 2018 17:04:14 -0500 +Subject: [PATCH] Add ArmorStand Item Meta + +This is adds basic item meta for armor stands. It does not add all +possible metadata however. + +There are armor, hand, and equipment types, as well as position data +that can also be added here. This initial addition should serve a +starting point for future additions in this area. + +Fixes GH-559 + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java +index c593499aee3a2737cd80739ce61e7fba133d11ec..6b460841a8428727dd55a841fe5af06de9b1bdd1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java +@@ -9,9 +9,22 @@ import org.bukkit.configuration.serialization.DelegateDeserialization; + import org.bukkit.craftbukkit.inventory.CraftMetaItem.ItemMetaKey; + + @DelegateDeserialization(CraftMetaItem.SerializableMeta.class) +-public class CraftMetaArmorStand extends CraftMetaItem { ++public class CraftMetaArmorStand extends CraftMetaItem implements com.destroystokyo.paper.inventory.meta.ArmorStandMeta { // Paper + + static final ItemMetaKey ENTITY_TAG = new ItemMetaKey("EntityTag", "entity-tag"); ++ // Paper start ++ static final ItemMetaKey INVISIBLE = new ItemMetaKey("Invisible", "invisible"); ++ static final ItemMetaKey NO_BASE_PLATE = new ItemMetaKey("NoBasePlate", "no-base-plate"); ++ static final ItemMetaKey SHOW_ARMS = new ItemMetaKey("ShowArms", "show-arms"); ++ static final ItemMetaKey SMALL = new ItemMetaKey("Small", "small"); ++ static final ItemMetaKey MARKER = new ItemMetaKey("Marker", "marker"); ++ ++ private boolean invisible; ++ private boolean noBasePlate; ++ private boolean showArms; ++ private boolean small; ++ private boolean marker; ++ // Paper end + NBTTagCompound entityTag; + + CraftMetaArmorStand(CraftMetaItem meta) { +@@ -22,6 +35,13 @@ public class CraftMetaArmorStand extends CraftMetaItem { + } + + CraftMetaArmorStand armorStand = (CraftMetaArmorStand) meta; ++ // Paper start ++ this.invisible = armorStand.invisible; ++ this.noBasePlate = armorStand.noBasePlate; ++ this.showArms = armorStand.showArms; ++ this.small = armorStand.small; ++ this.marker = armorStand.marker; ++ // Paper end + this.entityTag = armorStand.entityTag; + } + +@@ -30,11 +50,47 @@ public class CraftMetaArmorStand extends CraftMetaItem { + + if (tag.hasKey(ENTITY_TAG.NBT)) { + entityTag = tag.getCompound(ENTITY_TAG.NBT); ++ ++ // Paper start ++ if (entityTag.hasKey(INVISIBLE.NBT)) { ++ invisible = entityTag.getBoolean(INVISIBLE.NBT); ++ } ++ ++ if (entityTag.hasKey(NO_BASE_PLATE.NBT)) { ++ noBasePlate = entityTag.getBoolean(NO_BASE_PLATE.NBT); ++ } ++ ++ if (entityTag.hasKey(SHOW_ARMS.NBT)) { ++ showArms = entityTag.getBoolean(SHOW_ARMS.NBT); ++ } ++ ++ if (entityTag.hasKey(SMALL.NBT)) { ++ small = entityTag.getBoolean(SMALL.NBT); ++ } ++ ++ if (entityTag.hasKey(MARKER.NBT)) { ++ marker = entityTag.getBoolean(MARKER.NBT); ++ } ++ // Paper end + } + } + + CraftMetaArmorStand(Map map) { + super(map); ++ ++ // Paper start ++ boolean invis = SerializableMeta.getBoolean(map, INVISIBLE.BUKKIT); ++ boolean noBase = SerializableMeta.getBoolean(map, NO_BASE_PLATE.BUKKIT); ++ boolean showArms = SerializableMeta.getBoolean(map, SHOW_ARMS.BUKKIT); ++ boolean small = SerializableMeta.getBoolean(map, SMALL.BUKKIT); ++ boolean marker = SerializableMeta.getBoolean(map, MARKER.BUKKIT); ++ ++ this.invisible = invis; ++ this.noBasePlate = noBase; ++ this.showArms = showArms; ++ this.small = small; ++ this.marker = marker; ++ // Paper end + } + + @Override +@@ -57,6 +113,32 @@ public class CraftMetaArmorStand extends CraftMetaItem { + void applyToItem(NBTTagCompound tag) { + super.applyToItem(tag); + ++ // Paper start ++ if (!isArmorStandEmpty() && entityTag == null) { ++ entityTag = new NBTTagCompound(); ++ } ++ ++ if (isInvisible()) { ++ entityTag.setBoolean(INVISIBLE.NBT, invisible); ++ } ++ ++ if (hasNoBasePlate()) { ++ entityTag.setBoolean(NO_BASE_PLATE.NBT, noBasePlate); ++ } ++ ++ if (shouldShowArms()) { ++ entityTag.setBoolean(SHOW_ARMS.NBT, showArms); ++ } ++ ++ if (isSmall()) { ++ entityTag.setBoolean(SMALL.NBT, small); ++ } ++ ++ if (isMarker()) { ++ entityTag.setBoolean(MARKER.NBT, marker); ++ } ++ // Paper end ++ + if (entityTag != null) { + tag.set(ENTITY_TAG.NBT, entityTag); + } +@@ -78,7 +160,7 @@ public class CraftMetaArmorStand extends CraftMetaItem { + } + + boolean isArmorStandEmpty() { +- return !(entityTag != null); ++ return !(isInvisible() || hasNoBasePlate() || shouldShowArms() || isSmall() || isMarker() || entityTag != null); + } + + @Override +@@ -89,7 +171,13 @@ public class CraftMetaArmorStand extends CraftMetaItem { + if (meta instanceof CraftMetaArmorStand) { + CraftMetaArmorStand that = (CraftMetaArmorStand) meta; + +- return entityTag != null ? that.entityTag != null && this.entityTag.equals(that.entityTag) : entityTag == null; ++ // Paper start ++ return invisible == that.invisible && ++ noBasePlate == that.noBasePlate && ++ showArms == that.showArms && ++ small == that.small && ++ marker == that.marker; ++ // Paper end + } + return true; + } +@@ -104,9 +192,14 @@ public class CraftMetaArmorStand extends CraftMetaItem { + final int original; + int hash = original = super.applyHash(); + +- if (entityTag != null) { +- hash = 73 * hash + entityTag.hashCode(); +- } ++ // Paper start ++ hash += entityTag != null ? 73 * hash + entityTag.hashCode() : 0; ++ hash += isInvisible() ? 61 * hash + 1231 : 0; ++ hash += hasNoBasePlate() ? 61 * hash + 1231 : 0; ++ hash += shouldShowArms() ? 61 * hash + 1231 : 0; ++ hash += isSmall() ? 61 * hash + 1231 : 0; ++ hash += isMarker() ? 61 * hash + 1231 : 0; ++ // Paper end + + return original != hash ? CraftMetaArmorStand.class.hashCode() ^ hash : hash; + } +@@ -115,6 +208,28 @@ public class CraftMetaArmorStand extends CraftMetaItem { + Builder serialize(Builder builder) { + super.serialize(builder); + ++ // Paper start ++ if (isInvisible()) { ++ builder.put(INVISIBLE.BUKKIT, invisible); ++ } ++ ++ if (hasNoBasePlate()) { ++ builder.put(NO_BASE_PLATE.BUKKIT, noBasePlate); ++ } ++ ++ if (shouldShowArms()) { ++ builder.put(SHOW_ARMS.BUKKIT, showArms); ++ } ++ ++ if (isSmall()) { ++ builder.put(SMALL.BUKKIT, small); ++ } ++ ++ if (isMarker()) { ++ builder.put(MARKER.BUKKIT, marker); ++ } ++ // Paper end ++ + return builder; + } + +@@ -128,4 +243,56 @@ public class CraftMetaArmorStand extends CraftMetaItem { + + return clone; + } ++ ++ // Paper start ++ @Override ++ public boolean isInvisible() { ++ return invisible; ++ } ++ ++ @Override ++ public boolean hasNoBasePlate() { ++ return noBasePlate; ++ } ++ ++ @Override ++ public boolean shouldShowArms() { ++ return showArms; ++ } ++ ++ @Override ++ public boolean isSmall() { ++ return small; ++ } ++ ++ @Override ++ public boolean isMarker() { ++ return marker; ++ } ++ ++ @Override ++ public void setInvisible(boolean invisible) { ++ this.invisible = invisible; ++ } ++ ++ @Override ++ public void setNoBasePlate(boolean noBasePlate) { ++ this.noBasePlate = noBasePlate; ++ } ++ ++ @Override ++ public void setShowArms(boolean showArms) { ++ this.showArms = showArms; ++ } ++ ++ @Override ++ public void setSmall(boolean small) { ++ this.small = small; ++ } ++ ++ @Override ++ public void setMarker(boolean marker) { ++ this.marker = marker; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 57a6e66866ea82caccbbbfd55948a081f50f6bbe..7f790c484fec77e1d1f1dc6abe0daa19d009ae46 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -1442,6 +1442,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + CraftMetaCrossbow.CHARGED.NBT, + CraftMetaCrossbow.CHARGED_PROJECTILES.NBT, + CraftMetaSuspiciousStew.EFFECTS.NBT, ++ // Paper start ++ CraftMetaArmorStand.ENTITY_TAG.NBT, ++ CraftMetaArmorStand.INVISIBLE.NBT, ++ CraftMetaArmorStand.NO_BASE_PLATE.NBT, ++ CraftMetaArmorStand.SHOW_ARMS.NBT, ++ CraftMetaArmorStand.SMALL.NBT, ++ CraftMetaArmorStand.MARKER.NBT, ++ // Paper end + CraftMetaCompass.LODESTONE_DIMENSION.NBT, + CraftMetaCompass.LODESTONE_POS.NBT, + CraftMetaCompass.LODESTONE_TRACKED.NBT +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +index 9a351c137776ac622f4df7353bb353142b3a6ccc..42f577ed3508ba5a380648461e149f16ce97c9bd 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +@@ -313,6 +313,7 @@ public class ItemMetaTest extends AbstractTestingBase { + final CraftMetaArmorStand meta = (CraftMetaArmorStand) cleanStack.getItemMeta(); + meta.entityTag = new NBTTagCompound(); + meta.entityTag.setBoolean("Small", true); ++ meta.setInvisible(true); // Paper + cleanStack.setItemMeta(meta); + return cleanStack; + } diff --git a/patches/server-unmapped/0001/0193-Extend-Player-Interact-cancellation.patch b/patches/server-unmapped/0001/0193-Extend-Player-Interact-cancellation.patch new file mode 100644 index 0000000000..a0147364f6 --- /dev/null +++ b/patches/server-unmapped/0001/0193-Extend-Player-Interact-cancellation.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 11 Feb 2018 10:43:46 +0000 +Subject: [PATCH] Extend Player Interact cancellation + +GUIs are opened on the client, meaning that the server cannot block them from opening, +However, it is possible to close these GUIs from the server. + +Flower pots are also not updated on the client when interaction is cancelled, this patch +also resolves this. + +Update adjacent blocks of doors, double plants, pistons and beds +when cancelling interaction. + +diff --git a/src/main/java/net/minecraft/server/level/PlayerInteractManager.java b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +index 51157a9223f3da22d1110cfa211a502de59fb8a1..0ead8f1fabcc8debea8e2211d58a83b34acfcf0b 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerInteractManager.java ++++ b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +@@ -6,6 +6,7 @@ import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; + import net.minecraft.network.protocol.game.PacketPlayInBlockDig; + import net.minecraft.network.protocol.game.PacketPlayOutBlockBreak; ++import net.minecraft.network.protocol.game.PacketPlayOutCloseWindow; + import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo; + import net.minecraft.world.EnumHand; + import net.minecraft.world.EnumInteractionResult; +@@ -18,6 +19,7 @@ import net.minecraft.world.level.EnumGamemode; + import net.minecraft.world.level.World; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.BlockCommand; ++import net.minecraft.world.level.block.BlockFlowerPot; + import net.minecraft.world.level.block.BlockJigsaw; + import net.minecraft.world.level.block.BlockStructure; + import net.minecraft.world.level.block.entity.TileEntity; +@@ -179,6 +181,11 @@ public class PlayerInteractManager { + PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, blockposition, enumdirection, this.player.inventory.getItemInHand(), EnumHand.MAIN_HAND); + if (event.isCancelled()) { + // Let the client know the block still exists ++ // Paper start - brute force neighbor blocks for any attached blocks ++ for (EnumDirection dir : EnumDirection.values()) { ++ this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(world, blockposition.shift(dir))); ++ } ++ // Paper end + this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition)); + // Update any tile entity data for this block + TileEntity tileentity = this.world.getTileEntity(blockposition); +@@ -483,13 +490,20 @@ public class PlayerInteractManager { + interactItemStack = itemstack.cloneItemStack(); + + 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 BlockDoor) { + boolean bottom = iblockdata.get(BlockDoor.HALF) == BlockPropertyDoubleBlockHalf.LOWER; + entityplayer.playerConnection.sendPacket(new PacketPlayOutBlockChange(world, bottom ? blockposition.up() : blockposition.down())); + } else if (iblockdata.getBlock() instanceof BlockCake) { + entityplayer.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake ++ // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method ++ } else if (iblockdata.getBlock() instanceof BlockStructure) { ++ entityplayer.playerConnection.sendPacket(new PacketPlayOutCloseWindow()); ++ } else if (iblockdata.getBlock() instanceof BlockCommand) { ++ entityplayer.playerConnection.sendPacket(new PacketPlayOutCloseWindow()); + } ++ // Paper end - extend Player Interact cancellation + entityplayer.getBukkitEntity().updateInventory(); // SPIGOT-2867 + enuminteractionresult = (event.useItemInHand() != Event.Result.ALLOW) ? EnumInteractionResult.SUCCESS : EnumInteractionResult.PASS; + } else if (this.gamemode == EnumGamemode.SPECTATOR) { diff --git a/patches/server-unmapped/0001/0194-Tameable-getOwnerUniqueId-API.patch b/patches/server-unmapped/0001/0194-Tameable-getOwnerUniqueId-API.patch new file mode 100644 index 0000000000..eae20f237a --- /dev/null +++ b/patches/server-unmapped/0001/0194-Tameable-getOwnerUniqueId-API.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 24 Feb 2018 01:14:55 -0500 +Subject: [PATCH] Tameable#getOwnerUniqueId API + +This is faster if all you need is the UUID, as .getOwner() will cause +an OfflinePlayer to be loaded from disk. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +index 62ccef35e4b4238c50faf778fbf3ea9a494ca387..5b66165471197aad57e23f9a6669b11f25a5e4f2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +@@ -90,6 +90,9 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac + } + } + ++ public UUID getOwnerUniqueId() { ++ return getOwnerUUID(); ++ } + public UUID getOwnerUUID() { + return getHandle().getOwnerUUID(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java +index 3ac6623199f991ba16dbd62f4d6eed9982b22bcb..73a8ae346fb2ca5af172d96fa6b28e4d41a8c294 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java +@@ -17,6 +17,9 @@ public class CraftTameableAnimal extends CraftAnimals implements Tameable, Creat + return (EntityTameableAnimal) super.getHandle(); + } + ++ public UUID getOwnerUniqueId() { ++ return getOwnerUUID(); ++ } + public UUID getOwnerUUID() { + try { + return getHandle().getOwnerUUID(); diff --git a/patches/server-unmapped/0001/0195-Toggleable-player-crits-helps-mitigate-hacked-client.patch b/patches/server-unmapped/0001/0195-Toggleable-player-crits-helps-mitigate-hacked-client.patch new file mode 100644 index 0000000000..70c6b6d3e1 --- /dev/null +++ b/patches/server-unmapped/0001/0195-Toggleable-player-crits-helps-mitigate-hacked-client.patch @@ -0,0 +1,34 @@ +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, helps mitigate hacked clients. + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3c39f1bb3d88baaaed4dd43c51faeef89bb5c6c2..48f0385c7203c7955de5a015f3dc42be2ab7b681 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -192,6 +192,11 @@ public class PaperWorldConfig { + disableChestCatDetection = getBoolean("game-mechanics.disable-chest-cat-detection", false); + } + ++ public boolean disablePlayerCrits; ++ private void disablePlayerCrits() { ++ disablePlayerCrits = getBoolean("game-mechanics.disable-player-crits", false); ++ } ++ + public boolean allChunksAreSlimeChunks; + private void allChunksAreSlimeChunks() { + allChunksAreSlimeChunks = getBoolean("all-chunks-are-slime-chunks", false); +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index f9d0623a3ed5f49758cd5e97fe9f63a5b3198e58..18b0020d184e46c8957e82100681c8c66b1c3b62 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -1134,6 +1134,7 @@ public abstract class EntityHuman extends EntityLiving { + + boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround && !this.isClimbing() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && entity instanceof EntityLiving; + ++ flag2 = flag2 && !world.paperConfig.disablePlayerCrits; // Paper + flag2 = flag2 && !this.isSprinting(); + if (flag2) { + f *= 1.5F; diff --git a/patches/server-unmapped/0001/0196-Prevent-Frosted-Ice-from-loading-holding-chunks.patch b/patches/server-unmapped/0001/0196-Prevent-Frosted-Ice-from-loading-holding-chunks.patch new file mode 100644 index 0000000000..8ea2e3bb74 --- /dev/null +++ b/patches/server-unmapped/0001/0196-Prevent-Frosted-Ice-from-loading-holding-chunks.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 10 Mar 2018 16:33:15 -0500 +Subject: [PATCH] Prevent Frosted Ice from loading/holding chunks + + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockIceFrost.java b/src/main/java/net/minecraft/world/level/block/BlockIceFrost.java +index e32e94868386ff06ff29254e6cc3bee9b446a293..cbe8a6db356396f9fd9ce7cc61a5845bb8e6499d 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockIceFrost.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockIceFrost.java +@@ -40,7 +40,8 @@ public class BlockIceFrost extends BlockIce { + EnumDirection enumdirection = aenumdirection[j]; + + blockposition_mutableblockposition.a((BaseBlockPosition) blockposition, enumdirection); +- IBlockData iblockdata1 = worldserver.getType(blockposition_mutableblockposition); ++ IBlockData iblockdata1 = worldserver.getTypeIfLoaded(blockposition_mutableblockposition); // Paper ++ if (iblockdata1 == null) { continue; } // Paper + + if (iblockdata1.a((Block) this) && !this.e(iblockdata1, (World) worldserver, blockposition_mutableblockposition)) { + worldserver.getBlockTickList().a(blockposition_mutableblockposition, this, MathHelper.nextInt(random, worldserver.paperConfig.frostedIceDelayMin, worldserver.paperConfig.frostedIceDelayMax)); // Paper - use configurable min/max delay +@@ -83,7 +84,9 @@ public class BlockIceFrost extends BlockIce { + EnumDirection enumdirection = aenumdirection[l]; + + blockposition_mutableblockposition.a((BaseBlockPosition) blockposition, enumdirection); +- if (iblockaccess.getType(blockposition_mutableblockposition).a((Block) this)) { ++ // Paper start ++ IBlockData type = iblockaccess.getTypeIfLoaded(blockposition_mutableblockposition); ++ if (type != null && type.a((Block) this)) { // Paper end + ++j; + if (j >= i) { + return false; diff --git a/patches/server-unmapped/0001/0197-Disable-Explicit-Network-Manager-Flushing.patch b/patches/server-unmapped/0001/0197-Disable-Explicit-Network-Manager-Flushing.patch new file mode 100644 index 0000000000..c5919e14be --- /dev/null +++ b/patches/server-unmapped/0001/0197-Disable-Explicit-Network-Manager-Flushing.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 11 Mar 2018 14:13:33 -0400 +Subject: [PATCH] Disable Explicit Network Manager Flushing + +This seems completely pointless, as packet dispatch uses .writeAndFlush. + +Things seem to work fine without explicit flushing, but incase issues arise, +provide a System property to re-enable it using improved logic of doing the +flushing on the netty event loop, so it won't do the flush on the main thread. + +Renable flushing by passing -Dpaper.explicit-flush=true + +diff --git a/src/main/java/net/minecraft/network/NetworkManager.java b/src/main/java/net/minecraft/network/NetworkManager.java +index 60e4a4aa3854aaeb250d1318f2f25cf3591ea1d3..297820baef99e97e1216a64c527219e9ccc3e320 100644 +--- a/src/main/java/net/minecraft/network/NetworkManager.java ++++ b/src/main/java/net/minecraft/network/NetworkManager.java +@@ -73,6 +73,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + // Paper start - NetworkClient implementation + public int protocolVersion; + public java.net.InetSocketAddress virtualHost; ++ private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); + // Paper end + + public NetworkManager(EnumProtocolDirection enumprotocoldirection) { +@@ -240,7 +241,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + } + + if (this.channel != null) { +- this.channel.flush(); ++ if (enableExplicitFlush) this.channel.eventLoop().execute(() -> this.channel.flush()); // Paper - we don't need to explicit flush here, but allow opt in incase issues are found to a better version + } + + if (this.t++ % 20 == 0) { diff --git a/patches/server-unmapped/0001/0198-Implement-extended-PaperServerListPingEvent.patch b/patches/server-unmapped/0001/0198-Implement-extended-PaperServerListPingEvent.patch new file mode 100644 index 0000000000..6ad25474c6 --- /dev/null +++ b/patches/server-unmapped/0001/0198-Implement-extended-PaperServerListPingEvent.patch @@ -0,0 +1,283 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 15:56:26 +0200 +Subject: [PATCH] Implement extended PaperServerListPingEvent + + +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e7f1efd0466a5d7bb9584ffbd6fbac1ecc6153a5 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java +@@ -0,0 +1,31 @@ ++package com.destroystokyo.paper.network; ++ ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.EntityPlayer; ++import org.bukkit.entity.Player; ++import org.bukkit.util.CachedServerIcon; ++ ++import javax.annotation.Nullable; ++ ++class PaperServerListPingEventImpl extends PaperServerListPingEvent { ++ ++ private final MinecraftServer server; ++ ++ PaperServerListPingEventImpl(MinecraftServer server, StatusClient client, int protocolVersion, @Nullable CachedServerIcon icon) { ++ super(client, server.getMotd(), server.getPlayerCount(), server.getMaxPlayers(), ++ server.getServerModName() + ' ' + server.getVersion(), protocolVersion, icon); ++ this.server = server; ++ } ++ ++ @Override ++ protected final Object[] getOnlinePlayers() { ++ return this.server.getPlayerList().players.toArray(); ++ } ++ ++ @Override ++ protected final Player getBukkitPlayer(Object player) { ++ return ((EntityPlayer) player).getBukkitEntity(); ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java b/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java +new file mode 100644 +index 0000000000000000000000000000000000000000..46e84ac6ba5d32d030267fb0c991c281a673c716 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java +@@ -0,0 +1,11 @@ ++package com.destroystokyo.paper.network; ++ ++import net.minecraft.network.NetworkManager; ++ ++class PaperStatusClient extends PaperNetworkClient implements StatusClient { ++ ++ PaperStatusClient(NetworkManager networkManager) { ++ super(networkManager); ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..174326871df9b61beec51ef6a1e5f26932404b6a +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java +@@ -0,0 +1,110 @@ ++package com.destroystokyo.paper.network; ++ ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; ++import com.google.common.base.MoreObjects; ++import com.google.common.base.Strings; ++import com.mojang.authlib.GameProfile; ++import io.papermc.paper.adventure.AdventureComponent; ++import java.util.List; ++import java.util.UUID; ++import javax.annotation.Nonnull; ++import net.minecraft.network.NetworkManager; ++import net.minecraft.network.protocol.status.PacketStatusOutServerInfo; ++import net.minecraft.network.protocol.status.ServerPing; ++import net.minecraft.server.MinecraftServer; ++ ++public final class StandardPaperServerListPingEventImpl extends PaperServerListPingEventImpl { ++ ++ private static final GameProfile[] EMPTY_PROFILES = new GameProfile[0]; ++ private static final UUID FAKE_UUID = new UUID(0, 0); ++ ++ private GameProfile[] originalSample; ++ ++ private StandardPaperServerListPingEventImpl(MinecraftServer server, NetworkManager networkManager, ServerPing ping) { ++ super(server, new PaperStatusClient(networkManager), ping.getServerData() != null ? ping.getServerData().getProtocolVersion() : -1, server.server.getServerIcon()); ++ this.originalSample = ping.getPlayers() == null ? null : ping.getPlayers().getSample(); // GH-1473 - pre-tick race condition NPE ++ } ++ ++ @Nonnull ++ @Override ++ public List getPlayerSample() { ++ List sample = super.getPlayerSample(); ++ ++ if (this.originalSample != null) { ++ for (GameProfile profile : this.originalSample) { ++ sample.add(CraftPlayerProfile.asBukkitCopy(profile)); ++ } ++ this.originalSample = null; ++ } ++ ++ return sample; ++ } ++ ++ private GameProfile[] getPlayerSampleHandle() { ++ if (this.originalSample != null) { ++ return this.originalSample; ++ } ++ ++ List entries = super.getPlayerSample(); ++ if (entries.isEmpty()) { ++ return EMPTY_PROFILES; ++ } ++ ++ GameProfile[] profiles = new GameProfile[entries.size()]; ++ for (int i = 0; i < profiles.length; i++) { ++ /* ++ * Avoid null UUIDs/names since that will make the response invalid ++ * on the client. ++ * Instead, fall back to a fake/empty UUID and an empty string as name. ++ * This can be used to create custom lines in the player list that do not ++ * refer to a specific player. ++ */ ++ ++ PlayerProfile profile = entries.get(i); ++ if (profile.getId() != null && profile.getName() != null) { ++ profiles[i] = CraftPlayerProfile.asAuthlib(profile); ++ } else { ++ profiles[i] = new GameProfile(MoreObjects.firstNonNull(profile.getId(), FAKE_UUID), Strings.nullToEmpty(profile.getName())); ++ } ++ } ++ ++ return profiles; ++ } ++ ++ @SuppressWarnings("deprecation") ++ public static void processRequest(MinecraftServer server, NetworkManager networkManager) { ++ StandardPaperServerListPingEventImpl event = new StandardPaperServerListPingEventImpl(server, networkManager, server.getServerPing()); ++ server.server.getPluginManager().callEvent(event); ++ ++ // Close connection immediately if event is cancelled ++ if (event.isCancelled()) { ++ networkManager.close(null); ++ return; ++ } ++ ++ // Setup response ++ ServerPing ping = new ServerPing(); ++ ++ // Description ++ ping.setMOTD(new AdventureComponent(event.motd())); ++ ++ // Players ++ if (!event.shouldHidePlayers()) { ++ ping.setPlayerSample(new ServerPing.ServerPingPlayerSample(event.getMaxPlayers(), event.getNumPlayers())); ++ ping.getPlayers().setSample(event.getPlayerSampleHandle()); ++ } ++ ++ // Version ++ ping.setServerInfo(new ServerPing.ServerData(event.getVersion(), event.getProtocolVersion())); ++ ++ // Favicon ++ if (event.getServerIcon() != null) { ++ ping.setFavicon(event.getServerIcon().getData()); ++ } ++ ++ // Send response ++ networkManager.sendPacket(new PacketStatusOutServerInfo(ping)); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/network/protocol/status/PacketStatusOutServerInfo.java b/src/main/java/net/minecraft/network/protocol/status/PacketStatusOutServerInfo.java +index 0ebeacaaeb265d202f52c758566a5160c42e8a55..cb37805c8cc5a064391f338c6359df518f6db39a 100644 +--- a/src/main/java/net/minecraft/network/protocol/status/PacketStatusOutServerInfo.java ++++ b/src/main/java/net/minecraft/network/protocol/status/PacketStatusOutServerInfo.java +@@ -2,6 +2,7 @@ package net.minecraft.network.protocol.status; + + import com.google.gson.Gson; + import com.google.gson.GsonBuilder; ++import io.papermc.paper.adventure.AdventureComponent; // Paper + import java.io.IOException; + import net.minecraft.network.PacketDataSerializer; + import net.minecraft.network.chat.ChatModifier; +@@ -12,7 +13,9 @@ import net.minecraft.util.ChatTypeAdapterFactory; + + public class PacketStatusOutServerInfo implements Packet { + +- private static final Gson a = (new GsonBuilder()).registerTypeAdapter(ServerPing.ServerData.class, new ServerPing.ServerData.Serializer()).registerTypeAdapter(ServerPing.ServerPingPlayerSample.class, new ServerPing.ServerPingPlayerSample.Serializer()).registerTypeAdapter(ServerPing.class, new ServerPing.Serializer()).registerTypeHierarchyAdapter(IChatBaseComponent.class, new IChatBaseComponent.ChatSerializer()).registerTypeHierarchyAdapter(ChatModifier.class, new ChatModifier.ChatModifierSerializer()).registerTypeAdapterFactory(new ChatTypeAdapterFactory()).create(); ++ private static final Gson a = (new GsonBuilder()).registerTypeAdapter(ServerPing.ServerData.class, new ServerPing.ServerData.Serializer()).registerTypeAdapter(ServerPing.ServerPingPlayerSample.class, new ServerPing.ServerPingPlayerSample.Serializer()).registerTypeAdapter(ServerPing.class, new ServerPing.Serializer()).registerTypeHierarchyAdapter(IChatBaseComponent.class, new IChatBaseComponent.ChatSerializer()).registerTypeHierarchyAdapter(ChatModifier.class, new ChatModifier.ChatModifierSerializer()).registerTypeAdapterFactory(new ChatTypeAdapterFactory()) ++ .registerTypeAdapter(AdventureComponent.class, new AdventureComponent.Serializer()) ++ .create(); + private ServerPing b; + + public PacketStatusOutServerInfo() {} +diff --git a/src/main/java/net/minecraft/network/protocol/status/ServerPing.java b/src/main/java/net/minecraft/network/protocol/status/ServerPing.java +index 005ae7a75dfb19152abb606da29acad07c85e499..b9e36a83837913cd3e5abe598f695ba7a9ffc417 100644 +--- a/src/main/java/net/minecraft/network/protocol/status/ServerPing.java ++++ b/src/main/java/net/minecraft/network/protocol/status/ServerPing.java +@@ -31,6 +31,7 @@ public class ServerPing { + this.a = ichatbasecomponent; + } + ++ public ServerPingPlayerSample getPlayers() { return b(); } // Paper - OBFHELPER + public ServerPing.ServerPingPlayerSample b() { + return this.b; + } +@@ -162,10 +163,12 @@ public class ServerPing { + return this.b; + } + ++ public GameProfile[] getSample() { return c(); } // Paper - OBFHELPER + public GameProfile[] c() { + return this.c; + } + ++ public void setSample(GameProfile[] sample) { a(sample); } // Paper - OBFHELPER + public void a(GameProfile[] agameprofile) { + this.c = agameprofile; + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 62ee4708a3196cfa395317a6312b3ac6c036793a..dbf5a849358158324e8a5c87f831236b71f7ec0d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2,6 +2,9 @@ package net.minecraft.server; + + import com.google.common.base.Splitter; + import com.google.common.collect.ImmutableList; ++import co.aikar.timings.Timings; ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; ++import com.google.common.base.Stopwatch; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -1239,7 +1242,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant= 5000000000L) { + this.T = i; + this.serverPing.setPlayerSample(new ServerPing.ServerPingPlayerSample(this.getMaxPlayers(), this.getPlayerCount())); +- GameProfile[] agameprofile = new GameProfile[Math.min(this.getPlayerCount(), 12)]; ++ GameProfile[] agameprofile = new GameProfile[Math.min(this.getPlayerCount(), org.spigotmc.SpigotConfig.playerSample)]; // Paper + int j = MathHelper.nextInt(this.r, 0, this.getPlayerCount() - agameprofile.length); + + for (int k = 0; k < agameprofile.length; ++k) { +diff --git a/src/main/java/net/minecraft/server/network/PacketStatusListener.java b/src/main/java/net/minecraft/server/network/PacketStatusListener.java +index d219eda271a71f786808a6958b829fca40a1aaba..e1997563984540e6edf5d3b697d029dc5f3c40e1 100644 +--- a/src/main/java/net/minecraft/server/network/PacketStatusListener.java ++++ b/src/main/java/net/minecraft/server/network/PacketStatusListener.java +@@ -48,6 +48,8 @@ public class PacketStatusListener implements PacketStatusInListener { + this.networkManager.close(PacketStatusListener.a); + } else { + this.d = true; ++ // Paper start - Replace everything ++ /* + // CraftBukkit start + // this.networkManager.sendPacket(new PacketStatusOutServerInfo(this.minecraftServer.getServerPing())); + final Object[] players = minecraftServer.getPlayerList().players.toArray(); +@@ -143,6 +145,9 @@ public class PacketStatusListener implements PacketStatusInListener { + ping.setServerInfo(new ServerPing.ServerData(minecraftServer.getServerModName() + " " + minecraftServer.getVersion(), version)); + + this.networkManager.sendPacket(new PacketStatusOutServerInfo(ping)); ++ */ ++ com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(this.minecraftServer, this.networkManager); ++ // Paper end + } + // CraftBukkit end + } +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index e8e5e5b568ba53dd006f1461cb4f027ceeae5528..11f8a2e5bf43013bce8675ea310ff42eacf14754 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -289,7 +289,7 @@ public class SpigotConfig + public static int playerSample; + private static void playerSample() + { +- playerSample = getInt( "settings.sample-count", 12 ); ++ playerSample = Math.max( getInt( "settings.sample-count", 12 ), 0 ); // Paper - Avoid negative counts + Bukkit.getLogger().log( Level.INFO, "Server Ping Player Sample Count: {0}", playerSample ); // Paper - Use logger + } + diff --git a/patches/server-unmapped/0001/0199-Improved-Async-Task-Scheduler.patch b/patches/server-unmapped/0001/0199-Improved-Async-Task-Scheduler.patch new file mode 100644 index 0000000000..b0ae823221 --- /dev/null +++ b/patches/server-unmapped/0001/0199-Improved-Async-Task-Scheduler.patch @@ -0,0 +1,370 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 16 Mar 2018 22:59:43 -0400 +Subject: [PATCH] Improved Async Task Scheduler + +The Craft Scheduler still uses the primary thread for task scheduling. +This results in the main thread still having to do work as part of the +dispatching of async tasks. + +If plugins make use of lots of async tasks, such as particle emitters +that want to keep the logic off the main thread, the main thread still +receives quite a bit of load from processing all of these queued tasks. + +Additionally, resizing and managing the pending entries for all of +these asynchronous tasks takes up time on the main thread too. + +This commit replaces the implementation of the scheduler when working +with asynchronous tasks, by forwarding calls to the new scheduler. + +The Async Scheduler uses a single thread executor for "management" tasks. +The Management Thread is responsible for all adding and dispatching of +scheduled tasks. + +The mainThreadHeartbeat will send a heartbeat task to the management thread +with the currentTick value, so that it can find which tasks to execute. + +Scheduling of an async tasks also dispatches a management task, ensuring +that any Queue resizing operation occurs off of the main thread. + +The async queue uses a complete separate PriorityQueue, ensuring that resize +operations are decoupled from the sync tasks queue. + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3c1992e212a6d6f1db4d5b807b38d71913619fc0 +--- /dev/null ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +@@ -0,0 +1,122 @@ ++/* ++ * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++ ++package org.bukkit.craftbukkit.scheduler; ++ ++import com.destroystokyo.paper.ServerSchedulerReportingWrapper; ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import org.bukkit.plugin.Plugin; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.concurrent.Executor; ++import java.util.concurrent.Executors; ++import java.util.concurrent.SynchronousQueue; ++import java.util.concurrent.ThreadPoolExecutor; ++import java.util.concurrent.TimeUnit; ++ ++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()); ++ private final Executor management = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() ++ .setNameFormat("Craft Async Scheduler Management Thread").build()); ++ private final List temp = new ArrayList<>(); ++ ++ CraftAsyncScheduler() { ++ super(true); ++ executor.allowCoreThreadTimeOut(true); ++ executor.prestartAllCoreThreads(); ++ } ++ ++ @Override ++ public void cancelTask(int taskId) { ++ this.management.execute(() -> this.removeTask(taskId)); ++ } ++ ++ private synchronized void removeTask(int taskId) { ++ parsePending(); ++ this.pending.removeIf((task) -> { ++ if (task.getTaskId() == taskId) { ++ task.cancel0(); ++ return true; ++ } ++ return false; ++ }); ++ } ++ ++ @Override ++ public void mainThreadHeartbeat(int currentTick) { ++ this.currentTick = currentTick; ++ this.management.execute(() -> this.runTasks(currentTick)); ++ } ++ ++ private synchronized void runTasks(int currentTick) { ++ parsePending(); ++ while (!this.pending.isEmpty() && this.pending.peek().getNextRun() <= currentTick) { ++ CraftTask task = this.pending.remove(); ++ if (executeTask(task)) { ++ final long period = task.getPeriod(); ++ if (period > 0) { ++ task.setNextRun(currentTick + period); ++ temp.add(task); ++ } ++ } ++ parsePending(); ++ } ++ this.pending.addAll(temp); ++ temp.clear(); ++ } ++ ++ private boolean executeTask(CraftTask task) { ++ if (isValid(task)) { ++ this.runners.put(task.getTaskId(), task); ++ this.executor.execute(new ServerSchedulerReportingWrapper(task)); ++ return true; ++ } ++ return false; ++ } ++ ++ @Override ++ public synchronized void cancelTasks(Plugin plugin) { ++ parsePending(); ++ for (Iterator iterator = this.pending.iterator(); iterator.hasNext(); ) { ++ CraftTask task = iterator.next(); ++ if (task.getTaskId() != -1 && (plugin == null || task.getOwner().equals(plugin))) { ++ task.cancel0(); ++ iterator.remove(); ++ } ++ } ++ } ++ ++ /** ++ * Task is not cancelled ++ * @param runningTask ++ * @return ++ */ ++ static boolean isValid(CraftTask runningTask) { ++ return runningTask.getPeriod() >= CraftTask.NO_REPEATING; ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 0e0f361c3af363539d5d1d865603114bdb84fd67..ca90237a53c9a026919d28adaedf483ca3c7c2a8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -63,7 +63,7 @@ public class CraftScheduler implements BukkitScheduler { + /** + * Main thread logic only + */ +- private final PriorityQueue pending = new PriorityQueue(10, ++ final PriorityQueue pending = new PriorityQueue(10, // Paper + new Comparator() { + @Override + public int compare(final CraftTask o1, final CraftTask o2) { +@@ -80,12 +80,13 @@ public class CraftScheduler implements BukkitScheduler { + /** + * These are tasks that are currently active. It's provided for 'viewing' the current state. + */ +- private final ConcurrentHashMap runners = new ConcurrentHashMap(); ++ final ConcurrentHashMap runners = new ConcurrentHashMap(); // Paper + /** + * The sync task that is currently running on the main thread. + */ + private volatile CraftTask currentTask = null; +- private volatile int currentTick = -1; ++ // Paper start - Improved Async Task Scheduler ++ volatile int currentTick = -1;/* + private final Executor executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %d").build()); + private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) { + @Override +@@ -94,12 +95,31 @@ public class CraftScheduler implements BukkitScheduler { + } + }; + private CraftAsyncDebugger debugTail = debugHead; ++ ++ */ // Paper end + private static final int RECENT_TICKS; + + static { + RECENT_TICKS = 30; + } + ++ ++ // Paper start ++ private final CraftScheduler asyncScheduler; ++ private final boolean isAsyncScheduler; ++ public CraftScheduler() { ++ this(false); ++ } ++ ++ public CraftScheduler(boolean isAsync) { ++ this.isAsyncScheduler = isAsync; ++ if (isAsync) { ++ this.asyncScheduler = this; ++ } else { ++ this.asyncScheduler = new CraftAsyncScheduler(); ++ } ++ } ++ // Paper end + @Override + public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) { + return this.scheduleSyncDelayedTask(plugin, task, 0L); +@@ -222,7 +242,7 @@ public class CraftScheduler implements BukkitScheduler { + } else if (period < CraftTask.NO_REPEATING) { + period = CraftTask.NO_REPEATING; + } +- return handle(new CraftAsyncTask(runners, plugin, runnable, nextId(), period), delay); ++ return handle(new CraftAsyncTask(this.asyncScheduler.runners, plugin, runnable, nextId(), period), delay); // Paper + } + + @Override +@@ -238,6 +258,11 @@ public class CraftScheduler implements BukkitScheduler { + if (taskId <= 0) { + return; + } ++ // Paper start ++ if (!this.isAsyncScheduler) { ++ this.asyncScheduler.cancelTask(taskId); ++ } ++ // Paper end + CraftTask task = runners.get(taskId); + if (task != null) { + task.cancel0(); +@@ -280,6 +305,11 @@ public class CraftScheduler implements BukkitScheduler { + @Override + public void cancelTasks(final Plugin plugin) { + Validate.notNull(plugin, "Cannot cancel tasks of null plugin"); ++ // Paper start ++ if (!this.isAsyncScheduler) { ++ this.asyncScheduler.cancelTasks(plugin); ++ } ++ // Paper end + final CraftTask task = new CraftTask( + new Runnable() { + @Override +@@ -319,6 +349,13 @@ public class CraftScheduler implements BukkitScheduler { + + @Override + public boolean isCurrentlyRunning(final int taskId) { ++ // Paper start ++ if (!isAsyncScheduler) { ++ if (this.asyncScheduler.isCurrentlyRunning(taskId)) { ++ return true; ++ } ++ } ++ // Paper end + final CraftTask task = runners.get(taskId); + if (task == null) { + return false; +@@ -337,6 +374,11 @@ public class CraftScheduler implements BukkitScheduler { + if (taskId <= 0) { + return false; + } ++ // Paper start ++ if (!this.isAsyncScheduler && this.asyncScheduler.isQueued(taskId)) { ++ return true; ++ } ++ // Paper end + for (CraftTask task = head.getNext(); task != null; task = task.getNext()) { + if (task.getTaskId() == taskId) { + return task.getPeriod() >= CraftTask.NO_REPEATING; // The task will run +@@ -348,6 +390,12 @@ public class CraftScheduler implements BukkitScheduler { + + @Override + public List getActiveWorkers() { ++ // Paper start ++ if (!isAsyncScheduler) { ++ //noinspection TailRecursion ++ return this.asyncScheduler.getActiveWorkers(); ++ } ++ // Paper end + final ArrayList workers = new ArrayList(); + for (final CraftTask taskObj : runners.values()) { + // Iterator will be a best-effort (may fail to grab very new values) if called from an async thread +@@ -385,6 +433,11 @@ public class CraftScheduler implements BukkitScheduler { + pending.add(task); + } + } ++ // Paper start ++ if (!this.isAsyncScheduler) { ++ pending.addAll(this.asyncScheduler.getPendingTasks()); ++ } ++ // Paper end + return pending; + } + +@@ -392,6 +445,11 @@ public class CraftScheduler implements BukkitScheduler { + * This method is designed to never block or wait for locks; an immediate execution of all current tasks. + */ + public void mainThreadHeartbeat(final int currentTick) { ++ // Paper start ++ if (!this.isAsyncScheduler) { ++ this.asyncScheduler.mainThreadHeartbeat(currentTick); ++ } ++ // Paper end + this.currentTick = currentTick; + final List temp = this.temp; + parsePending(); +@@ -431,7 +489,7 @@ public class CraftScheduler implements BukkitScheduler { + parsePending(); + } else { + //debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper +- executor.execute(new ServerSchedulerReportingWrapper(task)); // Paper ++ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Paper"); // Paper + // We don't need to parse pending + // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) + } +@@ -450,7 +508,7 @@ public class CraftScheduler implements BukkitScheduler { + //debugHead = debugHead.getNextHead(currentTick); // Paper + } + +- private void addTask(final CraftTask task) { ++ protected void addTask(final CraftTask task) { + final AtomicReference tail = this.tail; + CraftTask tailTask = tail.get(); + while (!tail.compareAndSet(tailTask, task)) { +@@ -459,7 +517,13 @@ public class CraftScheduler implements BukkitScheduler { + tailTask.setNext(task); + } + +- private CraftTask handle(final CraftTask task, final long delay) { ++ protected CraftTask handle(final CraftTask task, final long delay) { // Paper ++ // Paper start ++ if (!this.isAsyncScheduler && !task.isSync()) { ++ this.asyncScheduler.handle(task, delay); ++ return task; ++ } ++ // Paper end + task.setNextRun(currentTick + delay); + addTask(task); + return task; +@@ -478,8 +542,8 @@ public class CraftScheduler implements BukkitScheduler { + return ids.incrementAndGet(); + } + +- private void parsePending() { +- MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); ++ void parsePending() { // Paper ++ if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); // Paper + CraftTask head = this.head; + CraftTask task = head.getNext(); + CraftTask lastTask = head; +@@ -498,7 +562,7 @@ public class CraftScheduler implements BukkitScheduler { + task.setNext(null); + } + this.head = lastTask; +- MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); ++ if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); // Paper + } + + private boolean isReady(final int currentTick) { diff --git a/patches/server-unmapped/0001/0200-Ability-to-change-PlayerProfile-in-AsyncPreLoginEven.patch b/patches/server-unmapped/0001/0200-Ability-to-change-PlayerProfile-in-AsyncPreLoginEven.patch new file mode 100644 index 0000000000..e59295a84a --- /dev/null +++ b/patches/server-unmapped/0001/0200-Ability-to-change-PlayerProfile-in-AsyncPreLoginEven.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 18 Mar 2018 11:45:57 -0400 +Subject: [PATCH] Ability to change PlayerProfile in AsyncPreLoginEvent + +This will allow you to change the players name or skin on login. + +diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java +index 714f44a668eb35b3c61bb9ab140f884917efd6f5..a903a073e6f5e8ae6ea383b786d930af69a966c5 100644 +--- a/src/main/java/net/minecraft/server/network/LoginListener.java ++++ b/src/main/java/net/minecraft/server/network/LoginListener.java +@@ -1,5 +1,7 @@ + package net.minecraft.server.network; + ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; + import com.mojang.authlib.GameProfile; + import com.mojang.authlib.exceptions.AuthenticationUnavailableException; + import java.math.BigInteger; +@@ -38,6 +40,7 @@ import org.apache.logging.log4j.Logger; + // CraftBukkit start + import net.minecraft.network.chat.ChatComponentText; + import io.papermc.paper.adventure.PaperAdventure; // Paper ++import org.bukkit.Bukkit; + import org.bukkit.craftbukkit.util.Waitable; + import org.bukkit.event.player.AsyncPlayerPreLoginEvent; + import org.bukkit.event.player.PlayerPreLoginEvent; +@@ -315,8 +318,16 @@ public class LoginListener implements PacketLoginInListener { + java.util.UUID uniqueId = i.getId(); + final org.bukkit.craftbukkit.CraftServer server = LoginListener.this.server.server; + +- AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId); ++ // Paper start ++ PlayerProfile profile = Bukkit.createProfile(uniqueId, playerName); ++ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId, profile); + server.getPluginManager().callEvent(asyncEvent); ++ profile = asyncEvent.getPlayerProfile(); ++ profile.complete(); ++ i = CraftPlayerProfile.asAuthlibCopy(profile); ++ playerName = i.getName(); ++ uniqueId = i.getId(); ++ // Paper end + + if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) { + final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId); diff --git a/patches/server-unmapped/0001/0201-Player.setPlayerProfile-API.patch b/patches/server-unmapped/0001/0201-Player.setPlayerProfile-API.patch new file mode 100644 index 0000000000..a800c632bf --- /dev/null +++ b/patches/server-unmapped/0001/0201-Player.setPlayerProfile-API.patch @@ -0,0 +1,142 @@ +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. + +diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java +index a903a073e6f5e8ae6ea383b786d930af69a966c5..2ce081e68fe27381d3e7f851b685cc547de35eb7 100644 +--- a/src/main/java/net/minecraft/server/network/LoginListener.java ++++ b/src/main/java/net/minecraft/server/network/LoginListener.java +@@ -56,7 +56,7 @@ public class LoginListener implements PacketLoginInListener { + public final NetworkManager networkManager; + private LoginListener.EnumProtocolState g; + private int h; +- private GameProfile i; ++ private GameProfile i; private void setGameProfile(final GameProfile profile) { this.i = profile; } private GameProfile getGameProfile() { return this.i; } // Paper - OBFHELPER + private final String j; + private SecretKey loginKey; + private EntityPlayer l; +@@ -319,12 +319,12 @@ public class LoginListener implements PacketLoginInListener { + final org.bukkit.craftbukkit.CraftServer server = LoginListener.this.server.server; + + // Paper start +- PlayerProfile profile = Bukkit.createProfile(uniqueId, playerName); ++ PlayerProfile profile = CraftPlayerProfile.asBukkitMirror(getGameProfile()); + AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId, profile); + server.getPluginManager().callEvent(asyncEvent); + profile = asyncEvent.getPlayerProfile(); +- profile.complete(); +- i = CraftPlayerProfile.asAuthlibCopy(profile); ++ profile.complete(true); ++ setGameProfile(CraftPlayerProfile.asAuthlib(profile)); + playerName = i.getName(); + uniqueId = i.getId(); + // Paper end +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 18b0020d184e46c8957e82100681c8c66b1c3b62..41dd46c6ef95f7dc41d9ca36a5f0b85f5608fdeb 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -162,7 +162,7 @@ public abstract class EntityHuman extends EntityLiving { + protected int bG; + protected final float bH = 0.02F; + private int g; +- private final GameProfile bJ; ++ private GameProfile bJ; public final void setProfile(final GameProfile profile) { this.bJ = profile; } // Paper - OBFHELPER + private ItemStack bL; + private final ItemCooldown bM; + @Nullable +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 4766a78a0562e5ae6e7d4850bd7b5d71425c3a0c..e6adf5ab609076bf1c25061429ed9aba1df1d9cb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -70,6 +70,7 @@ import net.minecraft.world.item.EnumColor; + import net.minecraft.world.item.enchantment.EnchantmentManager; + import net.minecraft.world.item.enchantment.Enchantments; + import net.minecraft.world.level.EnumGamemode; ++import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.block.entity.TileEntitySign; + import net.minecraft.world.level.saveddata.maps.MapIcon; + import net.minecraft.world.phys.Vec3D; +@@ -1312,8 +1313,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + hiddenPlayers.put(player.getUniqueId(), hidingPlugins); + + // Remove this player from the hidden player's EntityTrackerEntry +- PlayerChunkMap tracker = ((WorldServer) entity.world).getChunkProvider().playerChunkMap; ++ // Paper start + EntityPlayer other = ((CraftPlayer) player).getHandle(); ++ unregisterPlayer(other); ++ } ++ private void unregisterPlayer(EntityPlayer other) { ++ PlayerChunkMap tracker = ((WorldServer) entity.world).getChunkProvider().playerChunkMap; ++ // Paper end + PlayerChunkMap.EntityTracker entry = tracker.trackedEntities.get(other.getId()); + if (entry != null) { + entry.clear(getHandle()); +@@ -1354,8 +1360,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + hiddenPlayers.remove(player.getUniqueId()); + +- PlayerChunkMap tracker = ((WorldServer) entity.world).getChunkProvider().playerChunkMap; ++ // Paper start + EntityPlayer other = ((CraftPlayer) player).getHandle(); ++ registerPlayer(other); ++ } ++ private void registerPlayer(EntityPlayer other) { ++ PlayerChunkMap tracker = ((WorldServer) entity.world).getChunkProvider().playerChunkMap; ++ // Paper end + + getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, other)); + +@@ -1364,6 +1375,50 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + entry.updatePlayer(getHandle()); + } + } ++ // Paper start ++ private void reregisterPlayer(EntityPlayer player) { ++ if (!hiddenPlayers.containsKey(player.getUniqueID())) { ++ unregisterPlayer(player); ++ registerPlayer(player); ++ } ++ } ++ public void setPlayerProfile(com.destroystokyo.paper.profile.PlayerProfile profile) { ++ EntityPlayer self = getHandle(); ++ self.setProfile(com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile)); ++ if (!self.sentListPacket) { ++ return; ++ } ++ List players = server.getServer().getPlayerList().players; ++ for (EntityPlayer player : players) { ++ player.getBukkitEntity().reregisterPlayer(self); ++ } ++ refreshPlayer(); ++ } ++ public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() { ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(this).clone(); ++ } ++ ++ private void refreshPlayer() { ++ EntityPlayer handle = getHandle(); ++ ++ Location loc = getLocation(); ++ ++ PlayerConnection connection = handle.playerConnection; ++ reregisterPlayer(handle); ++ ++ //Respawn the player then update their position and selected slot ++ WorldServer worldserver = handle.getWorldServer(); ++ connection.sendPacket(new net.minecraft.network.protocol.game.PacketPlayOutRespawn(worldserver.getDimensionManager(), worldserver.getDimensionKey(), BiomeManager.a(worldserver.getSeed()), handle.playerInteractManager.getGameMode(), handle.playerInteractManager.c(), worldserver.isDebugWorld(), worldserver.isFlatWorld(), true)); ++ handle.updateAbilities(); ++ connection.sendPacket(new net.minecraft.network.protocol.game.PacketPlayOutPosition(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch(), new HashSet<>(), 0)); ++ net.minecraft.server.MinecraftServer.getServer().getPlayerList().updateClient(handle); ++ ++ if (this.isOp()) { ++ this.setOp(false); ++ this.setOp(true); ++ } ++ } ++ // Paper end + + public void removeDisconnectingPlayer(Player player) { + hiddenPlayers.remove(player.getUniqueId()); diff --git a/patches/server-unmapped/0001/0202-Fix-Dragon-Server-Crashes.patch b/patches/server-unmapped/0001/0202-Fix-Dragon-Server-Crashes.patch new file mode 100644 index 0000000000..1ec120745b --- /dev/null +++ b/patches/server-unmapped/0001/0202-Fix-Dragon-Server-Crashes.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 21 Mar 2018 20:52:07 -0400 +Subject: [PATCH] Fix Dragon Server Crashes + +If the dragon tries to find "ground" and hits a hole, or off edge, +it will infinitely keep looking for non air and eventually crash. + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerLandedFlame.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerLandedFlame.java +index fbecb61b8c511fc7daa21690b2a653254be74246..5adbd9fe858aad9c775a10254eb53b34719a9bd6 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerLandedFlame.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerLandedFlame.java +@@ -63,7 +63,7 @@ public class DragonControllerLandedFlame extends AbstractDragonControllerLanded + double d3 = d2; + BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(d0, d2, d1); + +- while (this.a.world.isEmpty(blockposition_mutableblockposition)) { ++ while (this.a.world.isEmpty(blockposition_mutableblockposition ) && d2 > 0) { // Paper + --d3; + if (d3 < 0.0D) { + d3 = d2; diff --git a/patches/server-unmapped/0001/0203-getPlayerUniqueId-API.patch b/patches/server-unmapped/0001/0203-getPlayerUniqueId-API.patch new file mode 100644 index 0000000000..2a817b65fb --- /dev/null +++ b/patches/server-unmapped/0001/0203-getPlayerUniqueId-API.patch @@ -0,0 +1,41 @@ +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 f1d4e2ac2823e2246463350b21f28c6d32f4f2c5..251427626f4dfdf9f5c4a2e6ef84cff30508fb76 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1511,6 +1511,26 @@ 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 (net.minecraft.server.MinecraftServer.getServer().getOnlineMode() ++ || (org.spigotmc.SpigotConfig.bungee && com.destroystokyo.paper.PaperConfig.bungeeOnlineMode)) { ++ profile = console.getUserCache().getProfile( 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); ++ } ++ return profile != null ? profile.getId() : null; ++ } ++ // Paper end ++ + @Override + @Deprecated + public OfflinePlayer getOfflinePlayer(String name) { diff --git a/patches/server-unmapped/0001/0204-Make-player-data-saving-configurable.patch b/patches/server-unmapped/0001/0204-Make-player-data-saving-configurable.patch new file mode 100644 index 0000000000..7a7b4d1993 --- /dev/null +++ b/patches/server-unmapped/0001/0204-Make-player-data-saving-configurable.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Mon, 26 Mar 2018 18:30:53 +0300 +Subject: [PATCH] Make player data saving configurable + +Upstream has added a patch which negates the need for this patch, +however, we should still migrate our configuration back upstream, +to prevent unexpected situations + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 52954fc3bf932cfc9d5ce63e3d3cace351305790..05a5abb951abe37f30a719cb75376d2d43c0d252 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -279,4 +279,13 @@ public class PaperConfig { + private static void authenticationServersDownKickMessage() { + authenticationServersDownKickMessage = Strings.emptyToNull(getString("messages.kick.authentication-servers-down", authenticationServersDownKickMessage)); + } ++ ++ private static void savePlayerData() { ++ Object val = config.get("settings.save-player-data"); ++ if (val instanceof Boolean) { ++ SpigotConfig.disablePlayerDataSaving = !(Boolean) val; ++ SpigotConfig.config.set("players.disable-saving", SpigotConfig.disableAdvancementSaving); ++ SpigotConfig.save(); ++ } ++ } + } diff --git a/patches/server-unmapped/0001/0205-Make-legacy-ping-handler-more-reliable.patch b/patches/server-unmapped/0001/0205-Make-legacy-ping-handler-more-reliable.patch new file mode 100644 index 0000000000..a437521ba2 --- /dev/null +++ b/patches/server-unmapped/0001/0205-Make-legacy-ping-handler-more-reliable.patch @@ -0,0 +1,168 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 18:22:50 +0200 +Subject: [PATCH] Make legacy ping handler more reliable + +The Minecraft server often fails to respond to old ("legacy") pings +from old Minecraft versions using the protocol used before the switch +to Netty in Minecraft 1.7. + +Due to packet fragmentation[1], we might not have all needed bytes +available when the LegacyPingHandler is called. In this case, it will +run into an error, remove the handler and continue using the modern +protocol. + +This is unlikely to happen for the first two revisions of the legacy +ping protocol (used in Minecraft 1.5.x and older) since the request +consists of only one or two bytes, but happens frequently for the +last/third revision introduced in Minecraft 1.6. + +It has much larger, variable packet sizes due to the inclusion of +the virtual host (the hostname/port used to connect to the server). + +The solution[2] is simple: If we find more than two matching bytes, +we buffer the remaining bytes until we have enough to fully read and +respond to the request. + +[1]: https://netty.io/wiki/user-guide-for-4.x.html#wiki-h3-11 +[2]: https://netty.io/wiki/user-guide-for-4.x.html#wiki-h4-13 + +diff --git a/src/main/java/net/minecraft/server/network/LegacyPingHandler.java b/src/main/java/net/minecraft/server/network/LegacyPingHandler.java +index e0edebf3eb93c11de2ed5c9013565950b4ad2375..0286d30b63e42224028b343315e1d1a9db2fe3d1 100644 +--- a/src/main/java/net/minecraft/server/network/LegacyPingHandler.java ++++ b/src/main/java/net/minecraft/server/network/LegacyPingHandler.java +@@ -15,6 +15,7 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + private static final Logger LOGGER = LogManager.getLogger(); + private final ServerConnection b; ++ private ByteBuf buf; // Paper + + public LegacyPingHandler(ServerConnection serverconnection) { + this.b = serverconnection; +@@ -23,6 +24,16 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) throws Exception { + ByteBuf bytebuf = (ByteBuf) object; + ++ // Paper start - Make legacy ping handler more reliable ++ if (this.buf != null) { ++ try { ++ readLegacy1_6(channelhandlercontext, bytebuf); ++ } finally { ++ bytebuf.release(); ++ } ++ return; ++ } ++ // Paper end + bytebuf.markReaderIndex(); + boolean flag = true; + +@@ -53,6 +64,10 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + this.a(channelhandlercontext, this.a(s)); + break; + default: ++ // Paper start - Replace with improved version below ++ if (bytebuf.readUnsignedByte() != 0x01 || bytebuf.readUnsignedByte() != 0xFA) return; ++ readLegacy1_6(channelhandlercontext, bytebuf); ++ /* + boolean flag1 = bytebuf.readUnsignedByte() == 1; + + flag1 &= bytebuf.readUnsignedByte() == 250; +@@ -76,6 +91,7 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + } finally { + bytebuf1.release(); + } ++ */ // Paper end - Replace with improved version below + } + + bytebuf.release(); +@@ -93,6 +109,90 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + } + ++ // Paper start ++ private static String readLegacyString(ByteBuf buf) { ++ int size = buf.readShort() * Character.BYTES; ++ if (!buf.isReadable(size)) { ++ return null; ++ } ++ ++ String result = buf.toString(buf.readerIndex(), size, StandardCharsets.UTF_16BE); ++ buf.skipBytes(size); // toString doesn't increase readerIndex automatically ++ return result; ++ } ++ ++ private void readLegacy1_6(ChannelHandlerContext ctx, ByteBuf part) { ++ ByteBuf buf = this.buf; ++ ++ if (buf == null) { ++ this.buf = buf = ctx.alloc().buffer(); ++ buf.markReaderIndex(); ++ } else { ++ buf.resetReaderIndex(); ++ } ++ ++ buf.writeBytes(part); ++ ++ if (!buf.isReadable(Short.BYTES + Short.BYTES + Byte.BYTES + Short.BYTES + Integer.BYTES)) { ++ return; ++ } ++ ++ String s = readLegacyString(buf); ++ if (s == null) { ++ return; ++ } ++ ++ if (!s.equals("MC|PingHost")) { ++ removeHandler(ctx); ++ return; ++ } ++ ++ if (!buf.isReadable(Short.BYTES) || !buf.isReadable(buf.readShort())) { ++ return; ++ } ++ ++ MinecraftServer server = this.b.d(); ++ int protocolVersion = buf.readByte(); ++ String host = readLegacyString(buf); ++ if (host == null) { ++ removeHandler(ctx); ++ return; ++ } ++ int port = buf.readInt(); ++ ++ if (buf.isReadable()) { ++ removeHandler(ctx); ++ return; ++ } ++ ++ buf.release(); ++ this.buf = null; ++ ++ LOGGER.debug("Ping: (1.6) from {}", ctx.channel().remoteAddress()); ++ ++ String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", ++ Byte.MAX_VALUE, server.getVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); ++ this.a(ctx, this.a(response)); ++ } ++ ++ private void removeHandler(ChannelHandlerContext ctx) { ++ ByteBuf buf = this.buf; ++ this.buf = null; ++ ++ buf.resetReaderIndex(); ++ ctx.pipeline().remove(this); ++ ctx.fireChannelRead(buf); ++ } ++ ++ @Override ++ public void handlerRemoved(ChannelHandlerContext ctx) { ++ if (this.buf != null) { ++ this.buf.release(); ++ this.buf = null; ++ } ++ } ++ // Paper end ++ + private void a(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf) { + channelhandlercontext.pipeline().firstContext().writeAndFlush(bytebuf).addListener(ChannelFutureListener.CLOSE); + } diff --git a/patches/server-unmapped/0001/0206-Call-PaperServerListPingEvent-for-legacy-pings.patch b/patches/server-unmapped/0001/0206-Call-PaperServerListPingEvent-for-legacy-pings.patch new file mode 100644 index 0000000000..9274b7f30a --- /dev/null +++ b/patches/server-unmapped/0001/0206-Call-PaperServerListPingEvent-for-legacy-pings.patch @@ -0,0 +1,154 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 19:30:51 +0200 +Subject: [PATCH] Call PaperServerListPingEvent for legacy pings + + +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java b/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java +new file mode 100644 +index 0000000000000000000000000000000000000000..74c012fd40491f1d870fbc1aa8c318a2197eb106 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java +@@ -0,0 +1,73 @@ ++package com.destroystokyo.paper.network; ++ ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; ++import net.minecraft.server.MinecraftServer; ++import org.apache.commons.lang3.StringUtils; ++import org.bukkit.ChatColor; ++ ++import java.net.InetSocketAddress; ++ ++import javax.annotation.Nullable; ++ ++public final class PaperLegacyStatusClient implements StatusClient { ++ ++ private final InetSocketAddress address; ++ private final int protocolVersion; ++ @Nullable private final InetSocketAddress virtualHost; ++ ++ private PaperLegacyStatusClient(InetSocketAddress address, int protocolVersion, @Nullable InetSocketAddress virtualHost) { ++ this.address = address; ++ this.protocolVersion = protocolVersion; ++ this.virtualHost = virtualHost; ++ } ++ ++ @Override ++ public InetSocketAddress getAddress() { ++ return this.address; ++ } ++ ++ @Override ++ public int getProtocolVersion() { ++ return this.protocolVersion; ++ } ++ ++ @Nullable ++ @Override ++ public InetSocketAddress getVirtualHost() { ++ return this.virtualHost; ++ } ++ ++ @Override ++ public boolean isLegacy() { ++ return true; ++ } ++ ++ public static PaperServerListPingEvent processRequest(MinecraftServer server, ++ InetSocketAddress address, int protocolVersion, @Nullable InetSocketAddress virtualHost) { ++ ++ PaperServerListPingEvent event = new PaperServerListPingEventImpl(server, ++ new PaperLegacyStatusClient(address, protocolVersion, virtualHost), Byte.MAX_VALUE, null); ++ server.server.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return null; ++ } ++ ++ return event; ++ } ++ ++ public static String getMotd(PaperServerListPingEvent event) { ++ return getFirstLine(event.getMotd()); ++ } ++ ++ public static String getUnformattedMotd(PaperServerListPingEvent event) { ++ // Strip color codes and all other occurrences of the color char (because it's used as delimiter) ++ return getFirstLine(StringUtils.remove(ChatColor.stripColor(event.getMotd()), ChatColor.COLOR_CHAR)); ++ } ++ ++ private static String getFirstLine(String s) { ++ int pos = s.indexOf('\n'); ++ return pos >= 0 ? s.substring(0, pos) : s; ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/network/LegacyPingHandler.java b/src/main/java/net/minecraft/server/network/LegacyPingHandler.java +index 0286d30b63e42224028b343315e1d1a9db2fe3d1..765dea6653b450802d45b76c7d2ac3d5778da9df 100644 +--- a/src/main/java/net/minecraft/server/network/LegacyPingHandler.java ++++ b/src/main/java/net/minecraft/server/network/LegacyPingHandler.java +@@ -1,5 +1,7 @@ + package net.minecraft.server.network; + ++import com.destroystokyo.paper.network.PaperLegacyStatusClient; ++ + import io.netty.buffer.ByteBuf; + import io.netty.buffer.Unpooled; + import io.netty.channel.ChannelFutureListener; +@@ -46,12 +48,19 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + MinecraftServer minecraftserver = this.b.d(); + int i = bytebuf.readableBytes(); + String s; +- org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(minecraftserver.server, inetsocketaddress.getAddress(), minecraftserver.getMotd(), minecraftserver.getPlayerCount(), minecraftserver.getMaxPlayers()); // CraftBukkit ++ //org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(minecraftserver.server, inetsocketaddress.getAddress(), minecraftserver.getMotd(), minecraftserver.getPlayerCount(), minecraftserver.getMaxPlayers()); // CraftBukkit // Paper ++ com.destroystokyo.paper.event.server.PaperServerListPingEvent event; // Paper + + switch (i) { + case 0: + LegacyPingHandler.LOGGER.debug("Ping: (<1.3.x) from {}:{}", inetsocketaddress.getAddress(), inetsocketaddress.getPort()); +- s = String.format("%s\u00a7%d\u00a7%d", event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit ++ // Paper start - Call PaperServerListPingEvent and use results ++ event = PaperLegacyStatusClient.processRequest(minecraftserver, inetsocketaddress, 39, null); ++ if (event == null) { ++ channelhandlercontext.close(); ++ break; ++ } ++ s = String.format("%s\u00a7%d\u00a7%d", PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers()); + this.a(channelhandlercontext, this.a(s)); + break; + case 1: +@@ -60,7 +69,14 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + } + + LegacyPingHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}:{}", inetsocketaddress.getAddress(), inetsocketaddress.getPort()); +- s = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, minecraftserver.getVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit ++ // Paper start - Call PaperServerListPingEvent and use results ++ event = PaperLegacyStatusClient.processRequest(minecraftserver, inetsocketaddress, 127, null); // Paper ++ if (event == null) { ++ channelhandlercontext.close(); ++ break; ++ } ++ s = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", new Object[] { event.getProtocolVersion(), minecraftserver.getVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()}); // CraftBukkit ++ // Paper end + this.a(channelhandlercontext, this.a(s)); + break; + default: +@@ -170,8 +186,16 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + LOGGER.debug("Ping: (1.6) from {}", ctx.channel().remoteAddress()); + +- String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", +- Byte.MAX_VALUE, server.getVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); ++ InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port); ++ com.destroystokyo.paper.event.server.PaperServerListPingEvent event = PaperLegacyStatusClient.processRequest( ++ server, (InetSocketAddress) ctx.channel().remoteAddress(), protocolVersion, virtualHost); ++ if (event == null) { ++ ctx.close(); ++ return; ++ } ++ ++ String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), event.getVersion(), ++ PaperLegacyStatusClient.getMotd(event), event.getNumPlayers(), event.getMaxPlayers()); + this.a(ctx, this.a(response)); + } + diff --git a/patches/server-unmapped/0001/0207-Flag-to-disable-the-channel-limit.patch b/patches/server-unmapped/0001/0207-Flag-to-disable-the-channel-limit.patch new file mode 100644 index 0000000000..a03a302879 --- /dev/null +++ b/patches/server-unmapped/0001/0207-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 e6adf5ab609076bf1c25061429ed9aba1df1d9cb..871c0e0b0c6df68c0f8c87828a01fe006d0646fb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -146,6 +146,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // Paper start + private org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; + private String resourcePackHash; ++ private static final boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null; // Paper - add a flag to disable the channel limit + // Paper end + + public CraftPlayer(CraftServer server, EntityPlayer entity) { +@@ -1581,7 +1582,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void addChannel(String channel) { +- Preconditions.checkState(channels.size() < 128, "Cannot register channel '%s'. Too many channels registered!", channel); ++ Preconditions.checkState(DISABLE_CHANNEL_LIMIT || channels.size() < 128, "Cannot register channel '%s'. Too many channels registered!", channel); // Paper - flag to disable channel limit + channel = StandardMessenger.validateAndCorrectChannel(channel); + if (channels.add(channel)) { + server.getPluginManager().callEvent(new PlayerRegisterChannelEvent(this, channel)); diff --git a/patches/server-unmapped/0001/0208-Add-method-to-open-already-placed-sign.patch b/patches/server-unmapped/0001/0208-Add-method-to-open-already-placed-sign.patch new file mode 100644 index 0000000000..203a050a82 --- /dev/null +++ b/patches/server-unmapped/0001/0208-Add-method-to-open-already-placed-sign.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Sun, 1 Apr 2018 02:29:37 +0300 +Subject: [PATCH] Add method to open already placed sign + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 8661f97ac885daca068057c1fcc4eed54c6d7f14..db7ad5a94d449f58a5749115776e61f448ff2f52 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -30,6 +30,7 @@ import net.minecraft.world.level.block.BlockWorkbench; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.entity.TileEntity; + import net.minecraft.world.level.block.entity.TileEntityContainer; ++import net.minecraft.world.level.block.entity.TileEntitySign; + import net.minecraft.world.level.block.state.IBlockData; + import org.bukkit.GameMode; + import org.bukkit.Location; +@@ -603,6 +604,17 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + } + } + ++ // Paper start - Add method to open already placed sign ++ @Override ++ public void openSign(org.bukkit.block.Sign sign) { ++ org.apache.commons.lang.Validate.isTrue(sign.getWorld().equals(this.getWorld()), "Sign must be in the same world as player is in"); ++ org.bukkit.craftbukkit.block.CraftSign craftSign = (org.bukkit.craftbukkit.block.CraftSign) sign; ++ TileEntitySign teSign = craftSign.getTileEntity(); ++ // Make sign editable temporarily, will be set back to false in PlayerConnection later ++ teSign.isEditable = true; ++ getHandle().openSign(teSign); ++ } ++ // Paper end + @Override + public boolean dropItem(boolean dropAll) { + return getHandle().dropItem(dropAll); diff --git a/patches/server-unmapped/0001/0209-Configurable-sprint-interruption-on-attack.patch b/patches/server-unmapped/0001/0209-Configurable-sprint-interruption-on-attack.patch new file mode 100644 index 0000000000..99fb21f6e5 --- /dev/null +++ b/patches/server-unmapped/0001/0209-Configurable-sprint-interruption-on-attack.patch @@ -0,0 +1,38 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 48f0385c7203c7955de5a015f3dc42be2ab7b681..cebf1a623a9bec72d60fdd23dda01868ef6431d4 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -358,4 +358,9 @@ public class PaperWorldConfig { + private void squidMaxSpawnHeight() { + squidMaxSpawnHeight = getDouble("squid-spawn-height.maximum", 0.0D); + } ++ ++ public boolean disableSprintInterruptionOnAttack; ++ private void disableSprintInterruptionOnAttack() { ++ disableSprintInterruptionOnAttack = getBoolean("game-mechanics.disable-sprint-interruption-on-attack", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 41dd46c6ef95f7dc41d9ca36a5f0b85f5608fdeb..7839553662d7f1f378969d42fb7a560e489852f4 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -1183,7 +1183,11 @@ public abstract class EntityHuman extends EntityLiving { + } + + this.setMot(this.getMot().d(0.6D, 1.0D, 0.6D)); +- this.setSprinting(false); ++ // Paper start - Configuration option to disable automatic sprint interruption ++ if (!world.paperConfig.disableSprintInterruptionOnAttack) { ++ this.setSprinting(false); ++ } ++ // Paper end + } + + if (flag3) { diff --git a/patches/server-unmapped/0001/0210-Fix-exploit-that-allowed-colored-signs-to-be-created.patch b/patches/server-unmapped/0001/0210-Fix-exploit-that-allowed-colored-signs-to-be-created.patch new file mode 100644 index 0000000000..e6e146f50d --- /dev/null +++ b/patches/server-unmapped/0001/0210-Fix-exploit-that-allowed-colored-signs-to-be-created.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 0x22 <0x22@futureclient.net> +Date: Thu, 26 Apr 2018 04:41:11 -0400 +Subject: [PATCH] Fix exploit that allowed colored signs to be created + + +diff --git a/src/main/java/net/minecraft/SharedConstants.java b/src/main/java/net/minecraft/SharedConstants.java +index b92975aa85fd79c70d6bec014284e8f55d0d3a4b..d5d6c545182b9b0b6aa5e52f1f9858450a17038e 100644 +--- a/src/main/java/net/minecraft/SharedConstants.java ++++ b/src/main/java/net/minecraft/SharedConstants.java +@@ -20,6 +20,7 @@ public class SharedConstants { + return c0 != 167 && c0 >= ' ' && c0 != 127; + } + ++ public static String filterAllowedChatCharacters(String input) { return a(input); } // Paper - OBFHELPER + public static String a(String s) { + StringBuilder stringbuilder = new StringBuilder(); + char[] achar = s.toCharArray(); +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index fc624315b156f450c1cbc87a81e9eeff5d31b4c2..d4862a3a7f523c13c452e7b67072261556162437 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -2787,7 +2787,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + List lines = new java.util.ArrayList<>(); + + for (int i = 0; i < list.size(); ++i) { +- lines.add(net.kyori.adventure.text.Component.text(list.get(i))); ++ lines.add(net.kyori.adventure.text.Component.text(SharedConstants.filterAllowedChatCharacters(list.get(i)))); // Paper - Replaced with anvil color stripping method to stop exploits that allow colored signs to be created. + } + SignChangeEvent event = new SignChangeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(worldserver, blockposition), this.getPlayer(), lines); + this.server.getPluginManager().callEvent(event); diff --git a/patches/server-unmapped/0001/0211-EndermanEscapeEvent.patch b/patches/server-unmapped/0001/0211-EndermanEscapeEvent.patch new file mode 100644 index 0000000000..428f29678a --- /dev/null +++ b/patches/server-unmapped/0001/0211-EndermanEscapeEvent.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 30 Apr 2018 13:15:55 -0400 +Subject: [PATCH] EndermanEscapeEvent + +Fires an event anytime an enderman intends to teleport away from the player + +You may cancel this, enabling ranged attacks to damage the enderman for example. + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java b/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java +index 2de3210bd8988b156b756723d0f781fd92bc151a..b889c1954df39b6180351c418393f5c772702589 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java +@@ -2,6 +2,7 @@ package net.minecraft.world.entity.monster; + + import java.util.EnumSet; + import java.util.Optional; ++import com.destroystokyo.paper.event.entity.EndermanEscapeEvent; // Paper + import java.util.Random; + import java.util.UUID; + import java.util.function.Predicate; +@@ -109,6 +110,12 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + setGoalTarget(entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason.UNKNOWN, true); + } + ++ // Paper start ++ private boolean tryEscape(EndermanEscapeEvent.Reason reason) { ++ return new EndermanEscapeEvent((org.bukkit.craftbukkit.entity.CraftEnderman) this.getBukkitEntity(), reason).callEvent(); ++ } ++ // Paper end ++ + @Override + public boolean setGoalTarget(EntityLiving entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent) { + if (!super.setGoalTarget(entityliving, reason, fireEvent)) { +@@ -262,7 +269,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + if (this.world.isDay() && this.ticksLived >= this.bs + 600) { + float f = this.aR(); + +- if (f > 0.5F && this.world.e(this.getChunkCoordinates()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F) { ++ if (f > 0.5F && this.world.e(this.getChunkCoordinates()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper + this.setGoalTarget((EntityLiving) null); + this.eL(); + } +@@ -360,17 +367,19 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + if (this.isInvulnerable(damagesource)) { + return false; + } else if (damagesource instanceof EntityDamageSourceIndirect) { ++ if (this.tryEscape(EndermanEscapeEvent.Reason.INDIRECT)) { // Paper start + for (int i = 0; i < 64; ++i) { + if (this.eL()) { + return true; + } + } ++ } // Paper end + + return false; + } else { + boolean flag = super.damageEntity(damagesource, f); + +- if (!this.world.s_() && !(damagesource.getEntity() instanceof EntityLiving) && this.random.nextInt(10) != 0) { ++ if (!this.world.s_() && !(damagesource.getEntity() instanceof EntityLiving) && this.random.nextInt(10) != 0 && this.tryEscape(damagesource == DamageSource.DROWN ? EndermanEscapeEvent.Reason.DROWN : EndermanEscapeEvent.Reason.INDIRECT)) { // Paper - use to be critical hits as else, but mojang removed critical hits in 1.16.2 due to MC-185684 + this.eL(); + } + +@@ -515,7 +524,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + + static class PathfinderGoalPlayerWhoLookedAtTarget extends PathfinderGoalNearestAttackableTarget { + +- private final EntityEnderman i; ++ private final EntityEnderman i; public final EntityEnderman getEnderman() { return this.i; } // Paper - OBFHELPER + private EntityHuman j; + private int k; + private int l; +@@ -578,7 +587,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + } else { + if (this.c != null && !this.i.isPassenger()) { + if (this.i.g((EntityHuman) this.c)) { +- if (this.c.h((Entity) this.i) < 16.0D) { ++ if (this.c.h((Entity) this.i) < 16.0D && this.getEnderman().tryEscape(EndermanEscapeEvent.Reason.STARE)) { // Paper + this.i.eL(); + } + diff --git a/patches/server-unmapped/0001/0212-Enderman.teleportRandomly.patch b/patches/server-unmapped/0001/0212-Enderman.teleportRandomly.patch new file mode 100644 index 0000000000..b64598e139 --- /dev/null +++ b/patches/server-unmapped/0001/0212-Enderman.teleportRandomly.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 30 Apr 2018 13:29:44 -0400 +Subject: [PATCH] Enderman.teleportRandomly() + +Ability to trigger the vanilla "teleport randomly" mechanic of an enderman. + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java b/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java +index b889c1954df39b6180351c418393f5c772702589..ef2f0211cd4d20dad0d3757c38c3c2882e99b2f2 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java +@@ -278,6 +278,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + super.mobTick(); + } + ++ public final boolean teleportRandomly() { return this.eL(); } // Paper - OBFHELPER + protected boolean eL() { + if (!this.world.s_() && this.isAlive()) { + double d0 = this.locX() + (this.random.nextDouble() - 0.5D) * 64.0D; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +index 8894d619796c7b81acde9ff27d0f9191122eade4..f54175a4b13ddedc475ef028942edb08eb4ff631 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +@@ -16,6 +16,7 @@ public class CraftEnderman extends CraftMonster implements Enderman { + super(server, entity); + } + ++ @Override public boolean teleportRandomly() { return getHandle().teleportRandomly(); } // Paper + @Override + public MaterialData getCarriedMaterial() { + IBlockData blockData = getHandle().getCarried(); diff --git a/patches/server-unmapped/0001/0213-Block-Enderpearl-Travel-Exploit.patch b/patches/server-unmapped/0001/0213-Block-Enderpearl-Travel-Exploit.patch new file mode 100644 index 0000000000..9ac01e9110 --- /dev/null +++ b/patches/server-unmapped/0001/0213-Block-Enderpearl-Travel-Exploit.patch @@ -0,0 +1,40 @@ +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. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index cebf1a623a9bec72d60fdd23dda01868ef6431d4..e8e1e7dafaf1c105b2f58cf3e118e3d665dc50ec 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -363,4 +363,10 @@ public class PaperWorldConfig { + private void disableSprintInterruptionOnAttack() { + disableSprintInterruptionOnAttack = getBoolean("game-mechanics.disable-sprint-interruption-on-attack", false); + } ++ ++ public boolean disableEnderpearlExploit = true; ++ private void disableEnderpearlExploit() { ++ disableEnderpearlExploit = getBoolean("game-mechanics.disable-unloaded-chunk-enderpearl-exploit", disableEnderpearlExploit); ++ log("Disable Unloaded Chunk Enderpearl Exploit: " + (disableEnderpearlExploit ? "enabled" : "disabled")); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java +index 33b3643922f1d703221afb538cda6c097ddbae43..65cea9282467cb362ac6e9e0bb03c5d36085ee43 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java +@@ -59,6 +59,7 @@ public abstract class IProjectile extends Entity { + protected void loadData(NBTTagCompound nbttagcompound) { + if (nbttagcompound.b("Owner")) { + this.shooter = nbttagcompound.a("Owner"); ++ if (this instanceof EntityEnderPearl && this.world != null && this.world.paperConfig.disableEnderpearlExploit) { this.shooter = null; } // Paper - Don't store shooter name for pearls to block enderpearl travel exploit + } + + this.d = nbttagcompound.getBoolean("LeftOwner"); diff --git a/patches/server-unmapped/0001/0214-Expand-World.spawnParticle-API-and-add-Builder.patch b/patches/server-unmapped/0001/0214-Expand-World.spawnParticle-API-and-add-Builder.patch new file mode 100644 index 0000000000..0278a9954f --- /dev/null +++ b/patches/server-unmapped/0001/0214-Expand-World.spawnParticle-API-and-add-Builder.patch @@ -0,0 +1,67 @@ +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/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 6137a88e1dc8d19a4e35ad97500dabeddba008a8..96d67fc3cbe61c1fb6e639b1838b2fd198c3f3d5 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -170,7 +170,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + public final Int2ObjectMap entitiesById = new Int2ObjectLinkedOpenHashMap(); + private final Map entitiesByUUID = Maps.newHashMap(); + private final Queue entitiesToAdd = Queues.newArrayDeque(); +- private final List players = Lists.newArrayList(); ++ public final List players = Lists.newArrayList(); // Paper - private -> public + public final ChunkProviderServer chunkProvider; // Paper - public + boolean tickingEntities; + private final MinecraftServer server; +@@ -1479,12 +1479,17 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + public int sendParticles(EntityPlayer 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 Expansion ++ return sendParticles(players, sender, t0, d0, d1, d2, i, d3, d4, d5, d6, force); ++ } ++ public int sendParticles(List receivers, EntityPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { ++ // Paper end + PacketPlayOutWorldParticles packetplayoutworldparticles = new PacketPlayOutWorldParticles(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) { +- EntityPlayer entityplayer = (EntityPlayer) this.players.get(k); ++ for (EntityHuman entityhuman : receivers) { // Paper - Particle API Expansion ++ EntityPlayer entityplayer = (EntityPlayer) entityhuman; // Paper - Particle API Expansion + if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit + + if (this.a(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 2513e9a5b66598337f5d380a036ee10fdbab38c3..3855b8264d3223f3b1609a3cb81e009b1cd61268 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2352,11 +2352,17 @@ public class CraftWorld 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 Expansion ++ spawnParticle(particle, null, null, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, force); ++ } ++ 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 + if (data != null && !particle.getDataType().isInstance(data)) { + throw new IllegalArgumentException("data should be " + particle.getDataType() + " got " + data.getClass()); + } + getHandle().sendParticles( +- null, // Sender ++ receivers == null ? getHandle().players : receivers.stream().map(player -> ((CraftPlayer) player).getHandle()).collect(java.util.stream.Collectors.toList()), // Paper - Particle API Expansion ++ sender != null ? ((CraftPlayer) sender).getHandle() : null, // Sender // Paper - Particle API Expansion + CraftParticle.toNMS(particle, data), // Particle + x, y, z, // Position + count, // Count diff --git a/patches/server-unmapped/0001/0215-EndermanAttackPlayerEvent.patch b/patches/server-unmapped/0001/0215-EndermanAttackPlayerEvent.patch new file mode 100644 index 0000000000..08c3406719 --- /dev/null +++ b/patches/server-unmapped/0001/0215-EndermanAttackPlayerEvent.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 1 May 2018 20:18:54 -0400 +Subject: [PATCH] EndermanAttackPlayerEvent + +Allow control over whether or not an enderman aggros a player. + +This allows you to override/extend the pumpkin/stare logic. + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java b/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java +index ef2f0211cd4d20dad0d3757c38c3c2882e99b2f2..aa6cb15637144c9d8db1b1861e58f3f02d68357a 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java +@@ -221,7 +221,15 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + this.a((WorldServer) this.world, nbttagcompound); + } + ++ // Paper start - OBFHELPER - ok not really, but verify this on updates + private boolean g(EntityHuman entityhuman) { ++ boolean shouldAttack = g_real(entityhuman); ++ com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) entityhuman.getBukkitEntity()); ++ event.setCancelled(!shouldAttack); ++ return event.callEvent(); ++ } ++ private boolean g_real(EntityHuman entityhuman) { ++ // Paper end + ItemStack itemstack = (ItemStack) entityhuman.inventory.armor.get(3); + + if (itemstack.getItem() == Blocks.CARVED_PUMPKIN.getItem()) { diff --git a/patches/server-unmapped/0001/0216-WitchConsumePotionEvent.patch b/patches/server-unmapped/0001/0216-WitchConsumePotionEvent.patch new file mode 100644 index 0000000000..4c85394244 --- /dev/null +++ b/patches/server-unmapped/0001/0216-WitchConsumePotionEvent.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 16 May 2018 20:35:16 -0400 +Subject: [PATCH] WitchConsumePotionEvent + +Fires when a witch consumes the potion in their hand + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java b/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java +index 814ef118e983ee6807108b2e07cd9b35ef9dae15..0bfdcbe4a792e8243de86ded6c64d930ec6e4de8 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java +@@ -124,7 +124,11 @@ public class EntityWitch extends EntityRaider implements IRangedEntity { + + this.setSlot(EnumItemSlot.MAINHAND, ItemStack.b); + if (itemstack.getItem() == Items.POTION) { +- List list = PotionUtil.getEffects(itemstack); ++ // Paper start ++ com.destroystokyo.paper.event.entity.WitchConsumePotionEvent event = new com.destroystokyo.paper.event.entity.WitchConsumePotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); ++ ++ List list = event.callEvent() ? PotionUtil.getEffects(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion())) : null; ++ // Paper end + + if (list != null) { + Iterator iterator = list.iterator(); diff --git a/patches/server-unmapped/0001/0217-WitchThrowPotionEvent.patch b/patches/server-unmapped/0001/0217-WitchThrowPotionEvent.patch new file mode 100644 index 0000000000..a9232360d4 --- /dev/null +++ b/patches/server-unmapped/0001/0217-WitchThrowPotionEvent.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 16 May 2018 20:44:58 -0400 +Subject: [PATCH] WitchThrowPotionEvent + +Fired when a witch throws a potion at a player + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java b/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java +index 0bfdcbe4a792e8243de86ded6c64d930ec6e4de8..2e65f6107ffecfe00c9c09baa60dec3021aac527 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java +@@ -224,9 +224,16 @@ public class EntityWitch extends EntityRaider implements IRangedEntity { + potionregistry = Potions.WEAKNESS; + } + ++ // Paper start ++ ItemStack potion = PotionUtil.a(new ItemStack(Items.SPLASH_POTION), potionregistry); ++ com.destroystokyo.paper.event.entity.WitchThrowPotionEvent event = new com.destroystokyo.paper.event.entity.WitchThrowPotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potion)); ++ if (!event.callEvent()) { ++ return; ++ } ++ potion = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); + EntityPotion entitypotion = new EntityPotion(this.world, this); +- +- entitypotion.setItem(PotionUtil.a(new ItemStack(Items.SPLASH_POTION), potionregistry)); ++ entitypotion.setItem(potion); ++ // Paper end + entitypotion.pitch -= -20.0F; + entitypotion.shoot(d0, d1 + (double) (f1 * 0.2F), d2, 0.75F, 8.0F); + if (!this.isSilent()) { diff --git a/patches/server-unmapped/0001/0218-Allow-spawning-Item-entities-with-World.spawnEntity.patch b/patches/server-unmapped/0001/0218-Allow-spawning-Item-entities-with-World.spawnEntity.patch new file mode 100644 index 0000000000..f3468383d8 --- /dev/null +++ b/patches/server-unmapped/0001/0218-Allow-spawning-Item-entities-with-World.spawnEntity.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 4 Jun 2018 20:39:20 -0400 +Subject: [PATCH] Allow spawning Item entities with World.spawnEntity + +This API has more capabilities than .dropItem with the Consumer function + +Item can be set inside of the Consumer pre spawn function. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 3855b8264d3223f3b1609a3cb81e009b1cd61268..f9af209170b5dccf425b2ca35c416ed61ccda3eb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1515,6 +1515,10 @@ public class CraftWorld implements World { + if (Boat.class.isAssignableFrom(clazz)) { + entity = new EntityBoat(world, x, y, z); + entity.setPositionRotation(x, y, z, yaw, pitch); ++ // Paper start ++ } else if (org.bukkit.entity.Item.class.isAssignableFrom(clazz)) { ++ entity = new EntityItem(world, x, y, z, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Item.getItemOf(net.minecraft.world.level.block.Blocks.DIRT))); ++ // Paper end + } else if (FallingBlock.class.isAssignableFrom(clazz)) { + entity = new EntityFallingBlock(world, x, y, z, world.getType(new BlockPosition(x, y, z))); + } else if (Projectile.class.isAssignableFrom(clazz)) { diff --git a/patches/server-unmapped/0001/0219-WitchReadyPotionEvent.patch b/patches/server-unmapped/0001/0219-WitchReadyPotionEvent.patch new file mode 100644 index 0000000000..379b2c1751 --- /dev/null +++ b/patches/server-unmapped/0001/0219-WitchReadyPotionEvent.patch @@ -0,0 +1,23 @@ +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/EntityWitch.java b/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java +index 2e65f6107ffecfe00c9c09baa60dec3021aac527..c6d79125e7dd982fc528ce61144005194cbaa323 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java +@@ -157,7 +157,11 @@ public class EntityWitch extends EntityRaider implements IRangedEntity { + } + + if (potionregistry != null) { +- this.setSlot(EnumItemSlot.MAINHAND, PotionUtil.a(new ItemStack(Items.POTION), potionregistry)); ++ // Paper start ++ ItemStack potion = PotionUtil.a(new ItemStack(Items.POTION), potionregistry); ++ org.bukkit.inventory.ItemStack bukkitStack = com.destroystokyo.paper.event.entity.WitchReadyPotionEvent.process((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potion)); ++ this.setSlot(EnumItemSlot.MAINHAND, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(bukkitStack)); ++ // Paper end + this.bq = this.getItemInMainHand().k(); + this.v(true); + if (!this.isSilent()) { diff --git a/patches/server-unmapped/0001/0220-ItemStack-getMaxItemUseDuration.patch b/patches/server-unmapped/0001/0220-ItemStack-getMaxItemUseDuration.patch new file mode 100644 index 0000000000..3c41349491 --- /dev/null +++ b/patches/server-unmapped/0001/0220-ItemStack-getMaxItemUseDuration.patch @@ -0,0 +1,37 @@ +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/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 58045d500a6fbb7eb568f48c7d8ce7730d357577..c525afbc7d73488db2cae1501cdbe80ec05aeb7c 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -606,6 +606,7 @@ public final class ItemStack { + this.getItem().b(this, world, entityhuman); + } + ++ public int getItemUseMaxDuration() { return k(); } // Paper - OBFHELPER + public int k() { + return this.getItem().e_(this); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 7221ac52c9f66ae0af6f6cbf15c8d47f9c0291a0..315addab147dfecf4aa88d32d154cefe850d0a78 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -174,6 +174,13 @@ public final class CraftItemStack extends ItemStack { + return (handle == null) ? Material.AIR.getMaxStackSize() : handle.getItem().getMaxStackSize(); + } + ++ // Paper start ++ @Override ++ public int getMaxItemUseDuration() { ++ return handle == null ? 0 : handle.getItemUseMaxDuration(); ++ } ++ // Paper end ++ + @Override + public void addUnsafeEnchantment(Enchantment ench, int level) { + Validate.notNull(ench, "Cannot add null enchantment"); diff --git a/patches/server-unmapped/0001/0221-Implement-EntityTeleportEndGatewayEvent.patch b/patches/server-unmapped/0001/0221-Implement-EntityTeleportEndGatewayEvent.patch new file mode 100644 index 0000000000..188e408423 --- /dev/null +++ b/patches/server-unmapped/0001/0221-Implement-EntityTeleportEndGatewayEvent.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 9 Jun 2018 14:08:39 +0200 +Subject: [PATCH] Implement EntityTeleportEndGatewayEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java +index 2808cd0b100bd65a730aba315ab47a59a4621b30..b7548d0b3938d95328fc86db4000190532eaa8f5 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java +@@ -194,9 +194,20 @@ public class TileEntityEndGateway extends TileEntityEnderPortal implements ITick + + } + // CraftBukkit end ++ // Paper start - EntityTeleportEndGatewayEvent - replicated from above ++ org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity = entity.getBukkitEntity(); ++ org.bukkit.Location location = new Location(world.getWorld(), (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D); ++ location.setPitch(bukkitEntity.getLocation().getPitch()); ++ location.setYaw(bukkitEntity.getLocation().getYaw()); ++ ++ com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent event = new com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent(bukkitEntity, bukkitEntity.getLocation(), location, new org.bukkit.craftbukkit.block.CraftEndGateway(MCUtil.toLocation(world, this.getPosition()).getBlock())); ++ if (!event.callEvent()) { ++ return; ++ } ++ // Paper end + + entity1.resetPortalCooldown(); +- entity1.enderTeleportAndLoad((double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D); ++ entity1.enderTeleportAndLoad(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ()); // Paper + } + + this.h(); diff --git a/patches/server-unmapped/0001/0222-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch b/patches/server-unmapped/0001/0222-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch new file mode 100644 index 0000000000..72ccc51e06 --- /dev/null +++ b/patches/server-unmapped/0001/0222-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 Jun 2018 01:18:49 -0400 +Subject: [PATCH] Unset Ignited flag on cancel of Explosion Event + +Otherwise the creeper infinite explodes + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java b/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java +index 08292753f925f33d75f3aca835c1fd19494b22ec..cbb973e077e04e5221bcc837f434b7093bdbcc2a 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java +@@ -48,7 +48,7 @@ public class EntityCreeper extends EntityMonster { + + private static final DataWatcherObject b = DataWatcher.a(EntityCreeper.class, DataWatcherRegistry.b); + private static final DataWatcherObject POWERED = DataWatcher.a(EntityCreeper.class, DataWatcherRegistry.i); +- private static final DataWatcherObject d = DataWatcher.a(EntityCreeper.class, DataWatcherRegistry.i); ++ private static final DataWatcherObject d = DataWatcher.a(EntityCreeper.class, DataWatcherRegistry.i); private static final DataWatcherObject isIgnitedDW = d; // Paper OBFHELPER + private int bo; + public int fuseTicks; // PAIL + public int maxFuseTicks = 30; +@@ -253,6 +253,7 @@ public class EntityCreeper extends EntityMonster { + this.createEffectCloud(); + } else { + fuseTicks = 0; ++ this.datawatcher.set(isIgnitedDW, Boolean.valueOf(false)); // Paper + } + // CraftBukkit end + } diff --git a/patches/server-unmapped/0001/0223-Fix-CraftEntity-hashCode.patch b/patches/server-unmapped/0001/0223-Fix-CraftEntity-hashCode.patch new file mode 100644 index 0000000000..bb0a09f3ce --- /dev/null +++ b/patches/server-unmapped/0001/0223-Fix-CraftEntity-hashCode.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 Jun 2018 20:20:15 -0400 +Subject: [PATCH] Fix CraftEntity hashCode + +hashCodes are not allowed to change, however bukkit used a value +that does change, the entityId. + +When an entity is teleported dimensions, the entity reference is +replaced with a new one with a new entity ID. + +For hashCode, we can simply use the UUID's hashCode to keep +the hashCode from changing. + +equals() is ok to use getEntityId() because equals() should only +be true if both the left and right are the same reference. + +Since entity ids can not duplicate during runtime, this +check is essentially the same as this.getHandle() == other.getHandle() + +However, replaced it too to make it clearer of intent. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 4c2a35fb33da19a15a220dc5e0c9fa3233d657fb..3642b17cafffd2818ee7a18d26bc25645f596115 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -745,14 +745,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return false; + } + final CraftEntity other = (CraftEntity) obj; +- return (this.getEntityId() == other.getEntityId()); ++ return (this.getHandle() == other.getHandle()); // Paper - while logically the same, this is clearer + } + ++ // Paper - Fix hashCode. entity ID's are not static. ++ // A CraftEntity can change reference to a new entity with a new ID, and hash codes should never change + @Override + public int hashCode() { +- int hash = 7; +- hash = 29 * hash + this.getEntityId(); +- return hash; ++ return getUniqueId().hashCode(); ++ // Paper end + } + + @Override diff --git a/patches/server-unmapped/0001/0224-Configurable-Alternative-LootPool-Luck-Formula.patch b/patches/server-unmapped/0001/0224-Configurable-Alternative-LootPool-Luck-Formula.patch new file mode 100644 index 0000000000..e0ee8431f3 --- /dev/null +++ b/patches/server-unmapped/0001/0224-Configurable-Alternative-LootPool-Luck-Formula.patch @@ -0,0 +1,110 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 15 Jun 2018 00:30:32 -0400 +Subject: [PATCH] Configurable Alternative LootPool Luck Formula + +Rewrites the Vanilla luck application formula so that luck can be +applied to items that do not have any quality defined. + +See: https://luckformula.emc.gs for data and details +----------- + +The rough summary is: +My goal was that in a pool, when luck was applied, the pool +rebalances so the percentages for bigger items is +lowered and smaller items is boosted. + +Do this by boosting and then reducing the weight value, +so that larger numbers are penalized more than smaller numbers. +resulting in a larger reduction of entries for more common +items than the reduction on small weights, +giving smaller weights more of a chance + +----------- + +This work kind of obsoletes quality, but quality would be useful +for 2 items with same weight that you want luck to impact +in varying directions. + +Fishing still falls into that as the weights are closer, so luck +will invalidate junk more. + +This change will result in some major changes to fishing formulas. + +----------- + +I would love to see this change in Vanilla, so Mojang please pull :) + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 05a5abb951abe37f30a719cb75376d2d43c0d252..77a03abd59db4a43f6f2d59d4c7ef176e782f205 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -288,4 +288,12 @@ public class PaperConfig { + SpigotConfig.save(); + } + } ++ ++ public static boolean useAlternativeLuckFormula = false; ++ private static void useAlternativeLuckFormula() { ++ useAlternativeLuckFormula = getBoolean("settings.use-alternative-luck-formula", false); ++ if (useAlternativeLuckFormula) { ++ Bukkit.getLogger().log(Level.INFO, "Using Aikar's Alternative Luck Formula to apply Luck attribute to all loot pool calculations. See https://luckformula.emc.gs"); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootSelectorEntry.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootSelectorEntry.java +index ceb9a1e1b1d55a0a8cd74189450f356b9ad4c46c..632002c4db01ca3f3c19aa583226cf36f17afe7f 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootSelectorEntry.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootSelectorEntry.java +@@ -20,8 +20,8 @@ import org.apache.commons.lang3.ArrayUtils; + + public abstract class LootSelectorEntry extends LootEntryAbstract { + +- protected final int c; +- protected final int e; ++ protected final int c; public int getWeight() { return c; } // Paper - OBFHELPER ++ protected final int e; public int getQuality() { return e; } // Paper - OBFHELPER + protected final LootItemFunction[] f; + private final BiFunction g; + private final LootEntry h = new LootSelectorEntry.c() { +@@ -152,11 +152,38 @@ public abstract class LootSelectorEntry extends LootEntryAbstract { + + public abstract class c implements LootEntry { + +- protected c() {} ++ protected c() { ++ } + + @Override + public int a(float f) { +- return Math.max(MathHelper.d((float) LootSelectorEntry.this.c + (float) LootSelectorEntry.this.e * f), 0); ++ // Paper start - Offer an alternative loot formula to refactor how luck bonus applies ++ // SEE: https://luckformula.emc.gs for details and data ++ if (lastLuck != null && lastLuck == f) { ++ return lastWeight; ++ } ++ // This is vanilla ++ float qualityModifer = (float) getQuality() * f; ++ double baseWeight = (getWeight() + qualityModifer); ++ if (com.destroystokyo.paper.PaperConfig.useAlternativeLuckFormula) { ++ // Random boost to avoid losing precision in the final int cast on return ++ final int weightBoost = 100; ++ baseWeight *= weightBoost; ++ // If we have vanilla 1, bump that down to 0 so nothing is is impacted ++ // vanilla 3 = 300, 200 basis = impact 2% ++ // =($B2*(($B2-100)/100/100)) ++ double impacted = baseWeight * ((baseWeight - weightBoost) / weightBoost / 100); ++ // =($B$7/100) ++ float luckModifier = Math.min(100, f * 10) / 100; ++ // =B2 - (C2 *($B$7/100)) ++ baseWeight = Math.ceil(baseWeight - (impacted * luckModifier)); ++ } ++ lastLuck = f; ++ lastWeight = (int) Math.max(0, Math.floor(baseWeight)); ++ return lastWeight; + } + } ++ private Float lastLuck = null; ++ private int lastWeight = 0; ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0225-Print-Error-details-when-failing-to-save-player-data.patch b/patches/server-unmapped/0001/0225-Print-Error-details-when-failing-to-save-player-data.patch new file mode 100644 index 0000000000..de373f15f0 --- /dev/null +++ b/patches/server-unmapped/0001/0225-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/WorldNBTStorage.java b/src/main/java/net/minecraft/world/level/storage/WorldNBTStorage.java +index 191c9e9a00b9871038f60d54bc22620322f6bdbd..4d30ca69dd303f1d76c8e6292021deda97851773 100644 +--- a/src/main/java/net/minecraft/world/level/storage/WorldNBTStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/WorldNBTStorage.java +@@ -43,7 +43,7 @@ public class WorldNBTStorage { + + SystemUtils.a(file1, file, file2); + } catch (Exception exception) { +- WorldNBTStorage.LOGGER.warn("Failed to save player data for {}", entityhuman.getDisplayName().getString()); ++ WorldNBTStorage.LOGGER.error("Failed to save player data for {}", entityhuman.getName(), exception); // Paper + } + + } diff --git a/patches/server-unmapped/0001/0226-Make-shield-blocking-delay-configurable.patch b/patches/server-unmapped/0001/0226-Make-shield-blocking-delay-configurable.patch new file mode 100644 index 0000000000..a569f09272 --- /dev/null +++ b/patches/server-unmapped/0001/0226-Make-shield-blocking-delay-configurable.patch @@ -0,0 +1,69 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index e8e1e7dafaf1c105b2f58cf3e118e3d665dc50ec..3e4bd1d6718d3ad2498fe9bd72eaac45044ecb77 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -369,4 +369,9 @@ public class PaperWorldConfig { + disableEnderpearlExploit = getBoolean("game-mechanics.disable-unloaded-chunk-enderpearl-exploit", disableEnderpearlExploit); + log("Disable Unloaded Chunk Enderpearl Exploit: " + (disableEnderpearlExploit ? "enabled" : "disabled")); + } ++ ++ public int shieldBlockingDelay = 5; ++ private void shieldBlockingDelay() { ++ shieldBlockingDelay = getInt("game-mechanics.shield-blocking-delay", 5); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 8a43a85a728c15dbc0fdd2fc8dc5dfff9a589358..d4ffff5ecb24af8439e5bfaa02e8136a10fdde90 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -3311,7 +3311,7 @@ public abstract class EntityLiving extends Entity { + if (this.isHandRaised() && !this.activeItem.isEmpty()) { + Item item = this.activeItem.getItem(); + +- return item.d_(this.activeItem) != EnumAnimation.BLOCK ? false : item.e_(this.activeItem) - this.bd >= 5; ++ return item.d_(this.activeItem) != EnumAnimation.BLOCK ? false : item.e_(this.activeItem) - this.bd >= getShieldBlockingDelay(); // Paper - shieldBlockingDelay + } else { + return false; + } +@@ -3567,4 +3567,15 @@ public abstract class EntityLiving extends Entity { + public void broadcastItemBreak(EnumHand enumhand) { + this.broadcastItemBreak(enumhand == EnumHand.MAIN_HAND ? EnumItemSlot.MAINHAND : EnumItemSlot.OFFHAND); + } ++ // Paper start ++ public int shieldBlockingDelay = world.paperConfig.shieldBlockingDelay; ++ ++ public int getShieldBlockingDelay() { ++ return shieldBlockingDelay; ++ } ++ ++ public void setShieldBlockingDelay(int shieldBlockingDelay) { ++ this.shieldBlockingDelay = shieldBlockingDelay; ++ } ++ // 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 c43c300963bae9bca6ab9c9389dd53e42318715c..2d1c54eed8fa6885837d63014ff1f4b33dd35bd7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -711,5 +711,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setArrowsStuck(int arrows) { + 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-unmapped/0001/0227-Improve-EntityShootBowEvent.patch b/patches/server-unmapped/0001/0227-Improve-EntityShootBowEvent.patch new file mode 100644 index 0000000000..4d145bf944 --- /dev/null +++ b/patches/server-unmapped/0001/0227-Improve-EntityShootBowEvent.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 15 Jun 2013 19:51:17 -0400 +Subject: [PATCH] Improve EntityShootBowEvent + +Adds missing call to Illagers and also adds Arrow ItemStack to skeltons + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityIllagerIllusioner.java b/src/main/java/net/minecraft/world/entity/monster/EntityIllagerIllusioner.java +index 54a4a295660375a0fcc54e02d80d569d9a32e73e..fee9a5140f097225b5da58b18bfbd528dffdc77b 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityIllagerIllusioner.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityIllagerIllusioner.java +@@ -171,8 +171,18 @@ public class EntityIllagerIllusioner extends EntityIllagerWizard implements IRan + double d3 = (double) MathHelper.sqrt(d0 * d0 + d2 * d2); + + entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.world.getDifficulty().a() * 4)); ++ // Paper start ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getItemInMainHand(), entityarrow.getOriginalItemStack(), entityarrow, entityliving.getRaisedHand(), 0.8F, true); ++ if (event.isCancelled()) { ++ event.getProjectile().remove(); ++ return; ++ } ++ ++ if (event.getProjectile() == entityarrow.getBukkitEntity()) { ++ this.world.addEntity(entityarrow); ++ } + this.playSound(SoundEffects.ENTITY_SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); +- this.world.addEntity(entityarrow); ++ // Paper end + } + + class a extends EntityIllagerWizard.PathfinderGoalCastSpell { +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntitySkeletonAbstract.java b/src/main/java/net/minecraft/world/entity/monster/EntitySkeletonAbstract.java +index a2a67bccf38464731670e98cb155348df94474c5..4dca5ea9127c15b2739483b2ad74a5296a6b96ad 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntitySkeletonAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntitySkeletonAbstract.java +@@ -197,7 +197,7 @@ public abstract class EntitySkeletonAbstract extends EntityMonster implements IR + + entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.world.getDifficulty().a() * 4)); + // CraftBukkit start +- org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getItemInMainHand(), null, entityarrow, net.minecraft.world.EnumHand.MAIN_HAND, 0.8F, true); ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getItemInMainHand(), entityarrow.getOriginalItemStack(), entityarrow, net.minecraft.world.EnumHand.MAIN_HAND, 0.8F, true); // Paper + if (event.isCancelled()) { + event.getProjectile().remove(); + return; diff --git a/patches/server-unmapped/0001/0228-PlayerReadyArrowEvent.patch b/patches/server-unmapped/0001/0228-PlayerReadyArrowEvent.patch new file mode 100644 index 0000000000..48c889f719 --- /dev/null +++ b/patches/server-unmapped/0001/0228-PlayerReadyArrowEvent.patch @@ -0,0 +1,39 @@ +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/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 7839553662d7f1f378969d42fb7a560e489852f4..408732c59ed817c056671a78e43a734b048a818e 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -2142,6 +2142,17 @@ public abstract class EntityHuman extends EntityLiving { + return ImmutableList.of(EntityPose.STANDING, EntityPose.CROUCHING, EntityPose.SWIMMING); + } + ++ // Paper start ++ protected boolean tryReadyArrow(ItemStack bow, ItemStack itemstack) { ++ return !(this instanceof EntityPlayer) || ++ new com.destroystokyo.paper.event.player.PlayerReadyArrowEvent( ++ ((EntityPlayer) this).getBukkitEntity(), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(bow), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack) ++ ).callEvent(); ++ // Paper end ++ } ++ + @Override + public ItemStack f(ItemStack itemstack) { + if (!(itemstack.getItem() instanceof ItemProjectileWeapon)) { +@@ -2158,7 +2169,7 @@ public abstract class EntityHuman extends EntityLiving { + for (int i = 0; i < this.inventory.getSize(); ++i) { + ItemStack itemstack2 = this.inventory.getItem(i); + +- if (predicate.test(itemstack2)) { ++ if (predicate.test(itemstack2) && tryReadyArrow(itemstack, itemstack2)) { // Paper + return itemstack2; + } + } diff --git a/patches/server-unmapped/0001/0229-Implement-EntityKnockbackByEntityEvent.patch b/patches/server-unmapped/0001/0229-Implement-EntityKnockbackByEntityEvent.patch new file mode 100644 index 0000000000..b18f3dbaa7 --- /dev/null +++ b/patches/server-unmapped/0001/0229-Implement-EntityKnockbackByEntityEvent.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Mon, 18 Jun 2018 15:46:23 +0200 +Subject: [PATCH] Implement EntityKnockbackByEntityEvent + +This event is called when an entity receives knockback by another entity. + +diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +index c22b5f8fcdd4aa7dac242f634ef73edcd8745fc6..41566398f5eee6cf93376f2e2200728bb6d2181c 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java ++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +@@ -1568,7 +1568,7 @@ public abstract class EntityInsentient extends EntityLiving { + + if (flag) { + if (f1 > 0.0F && entity instanceof EntityLiving) { +- ((EntityLiving) entity).a(f1 * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F))); ++ ((EntityLiving) entity).doKnockback(f1 * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this); // Paper + this.setMot(this.getMot().d(0.6D, 1.0D, 0.6D)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index d4ffff5ecb24af8439e5bfaa02e8136a10fdde90..4798eac1498ebeecf0476a61a093d3871c00d5a5 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -1339,7 +1339,7 @@ public abstract class EntityLiving extends Entity { + } + + this.ap = (float) (MathHelper.d(d1, d0) * 57.2957763671875D - (double) this.yaw); +- this.a(0.4F, d0, d1); ++ this.doKnockback(0.4F, d0, d1, entity1); // Paper + } else { + this.ap = (float) ((int) (Math.random() * 2.0D) * 180); + } +@@ -1387,7 +1387,7 @@ public abstract class EntityLiving extends Entity { + } + + protected void e(EntityLiving entityliving) { +- entityliving.a(0.5F, entityliving.locX() - this.locX(), entityliving.locZ() - this.locZ()); ++ entityliving.doKnockback(0.5F, entityliving.locX() - this.locX(), entityliving.locZ() - this.locZ(), this); // Paper + } + + private boolean f(DamageSource damagesource) { +@@ -1630,6 +1630,11 @@ public abstract class EntityLiving extends Entity { + } + + public void a(float f, double d0, double d1) { ++ // Paper start - add knockbacking entity parameter ++ this.doKnockback(f, d0, d1, null); ++ } ++ public void doKnockback(float f, double d0, double d1, Entity knockingBackEntity) { ++ // Paper end - add knockbacking entity parameter + f = (float) ((double) f * (1.0D - this.b(GenericAttributes.KNOCKBACK_RESISTANCE))); + if (f > 0.0F) { + this.impulse = true; +@@ -1637,6 +1642,16 @@ public abstract class EntityLiving extends Entity { + Vec3D vec3d1 = (new Vec3D(d0, 0.0D, d1)).d().a((double) f); + + this.setMot(vec3d.x / 2.0D - vec3d1.x, this.onGround ? Math.min(0.4D, vec3d.y / 2.0D + (double) f) : vec3d.y, vec3d.z / 2.0D - vec3d1.z); ++ ++ // Paper start - call EntityKnockbackByEntityEvent ++ Vec3D currentMot = this.getMot(); ++ org.bukkit.util.Vector delta = new org.bukkit.util.Vector(currentMot.x - vec3d.x, currentMot.y - vec3d.y, currentMot.z - vec3d.z); ++ // Restore old velocity to be able to access it in the event ++ this.setMot(vec3d); ++ if (knockingBackEntity == null || new com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent((LivingEntity) getBukkitEntity(), knockingBackEntity.getBukkitEntity(), f, delta).callEvent()) { ++ this.setMot(vec3d.x + delta.getX(), vec3d.y + delta.getY(), vec3d.z + delta.getZ()); ++ } ++ // Paper end + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 408732c59ed817c056671a78e43a734b048a818e..3c49d7acd4ad0717886adf6c469e8a49a58e859b 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -1177,7 +1177,7 @@ public abstract class EntityHuman extends EntityLiving { + if (flag5) { + if (i > 0) { + if (entity instanceof EntityLiving) { +- ((EntityLiving) entity).a((float) i * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F))); ++ ((EntityLiving) entity).doKnockback((float) i * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this); // Paper + } else { + entity.i((double) (-MathHelper.sin(this.yaw * 0.017453292F) * (float) i * 0.5F), 0.1D, (double) (MathHelper.cos(this.yaw * 0.017453292F) * (float) i * 0.5F)); + } +@@ -1201,7 +1201,7 @@ public abstract class EntityHuman extends EntityLiving { + if (entityliving != this && entityliving != entity && !this.r(entityliving) && (!(entityliving instanceof EntityArmorStand) || !((EntityArmorStand) entityliving).isMarker()) && this.h((Entity) entityliving) < 9.0D) { + // CraftBukkit start - Only apply knockback if the damage hits + if (entityliving.damageEntity(DamageSource.playerAttack(this).sweep(), f4)) { +- entityliving.a(0.4F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F))); ++ entityliving.doKnockback(0.4F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this); // Paper + } + // CraftBukkit end + } diff --git a/patches/server-unmapped/0001/0230-Expand-Explosions-API.patch b/patches/server-unmapped/0001/0230-Expand-Explosions-API.patch new file mode 100644 index 0000000000..1ae7cd1b54 --- /dev/null +++ b/patches/server-unmapped/0001/0230-Expand-Explosions-API.patch @@ -0,0 +1,23 @@ +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 f9af209170b5dccf425b2ca35c416ed61ccda3eb..36c9ee703660d0ef558481ef0fea0984502bc14e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -889,6 +889,11 @@ public class CraftWorld implements World { + public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source) { + return !world.createExplosion(source == null ? null : ((CraftEntity) source).getHandle(), x, y, z, power, setFire, breakBlocks ? Explosion.Effect.BREAK : Explosion.Effect.NONE).wasCanceled; + } ++ // Paper start ++ public boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks) { ++ return !world.createExplosion(source != null ? ((org.bukkit.craftbukkit.entity.CraftEntity) source).getHandle() : null, loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks ? Explosion.Effect.BREAK : Explosion.Effect.NONE).wasCanceled; ++ } ++ // Paper end + + @Override + public boolean createExplosion(Location loc, float power) { diff --git a/patches/server-unmapped/0001/0231-LivingEntity-Hand-Raised-Item-Use-API.patch b/patches/server-unmapped/0001/0231-LivingEntity-Hand-Raised-Item-Use-API.patch new file mode 100644 index 0000000000..4f4303b92c --- /dev/null +++ b/patches/server-unmapped/0001/0231-LivingEntity-Hand-Raised-Item-Use-API.patch @@ -0,0 +1,63 @@ +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/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 4798eac1498ebeecf0476a61a093d3871c00d5a5..2b10ae84ee8e9f63382d732e8c051fc47f0c5d9f 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -212,7 +212,7 @@ public abstract class EntityLiving extends Entity { + private float bu; + private int jumpTicks; + private float bw; +- protected ItemStack activeItem; ++ public ItemStack activeItem; // Paper - public + protected int bd; + protected int be; + private BlockPosition bx; +@@ -3294,10 +3294,12 @@ public abstract class EntityLiving extends Entity { + return this.activeItem; + } + ++ public int getItemUseRemainingTime() { return this.dZ(); } // Paper - OBFHELPER + public int dZ() { + return this.bd; + } + ++ public int getHandRaisedTime() { return this.ea(); } // Paper - OBFHELPER + public int ea() { + return this.isHandRaised() ? this.activeItem.k() - this.dZ() : 0; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 2d1c54eed8fa6885837d63014ff1f4b33dd35bd7..bd24b9865f37c34ffd63cd411ddc84abe5ab30d0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -721,5 +721,25 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setShieldBlockingDelay(int delay) { + getHandle().setShieldBlockingDelay(delay); + } ++ ++ @Override ++ public ItemStack getActiveItem() { ++ return getHandle().activeItem.asBukkitMirror(); ++ } ++ ++ @Override ++ public int getItemUseRemainingTime() { ++ return getHandle().getItemUseRemainingTime(); ++ } ++ ++ @Override ++ public int getHandRaisedTime() { ++ return getHandle().getHandRaisedTime(); ++ } ++ ++ @Override ++ public boolean isHandRaised() { ++ return getHandle().isHandRaised(); ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0232-RangedEntity-API.patch b/patches/server-unmapped/0001/0232-RangedEntity-API.patch new file mode 100644 index 0000000000..e0765e9b69 --- /dev/null +++ b/patches/server-unmapped/0001/0232-RangedEntity-API.patch @@ -0,0 +1,171 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 26 Jun 2018 22:00:49 -0400 +Subject: [PATCH] RangedEntity API + +Allows you to determine if an entity is capable of ranged attacks, +and to perform an attack. + +diff --git a/src/main/java/com/destroystokyo/paper/entity/CraftRangedEntity.java b/src/main/java/com/destroystokyo/paper/entity/CraftRangedEntity.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0195d4036f06db0f3f56f134dbfbc4360d44ed86 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/CraftRangedEntity.java +@@ -0,0 +1,19 @@ ++package com.destroystokyo.paper.entity; ++ ++import net.minecraft.world.entity.monster.IRangedEntity; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.entity.LivingEntity; ++ ++public interface CraftRangedEntity extends RangedEntity { ++ T getHandle(); ++ ++ @Override ++ default void rangedAttack(LivingEntity target, float charge) { ++ getHandle().rangedAttack(((CraftLivingEntity) target).getHandle(), charge); ++ } ++ ++ @Override ++ default void setChargingAttack(boolean raiseHands) { ++ getHandle().setChargingAttack(raiseHands); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/monster/IRangedEntity.java b/src/main/java/net/minecraft/world/entity/monster/IRangedEntity.java +index d79e6b28c77edc468c6471d909306c2135b496c7..0f0aaa8a15301dea8405e26333d30b385831506c 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/IRangedEntity.java ++++ b/src/main/java/net/minecraft/world/entity/monster/IRangedEntity.java +@@ -4,5 +4,8 @@ import net.minecraft.world.entity.EntityLiving; + + public interface IRangedEntity { + +- void a(EntityLiving entityliving, float f); ++ void a(EntityLiving entityliving, float f); default void rangedAttack(EntityLiving entityliving, float f) { a(entityliving, f); } // Paper - OBFHELPER ++ ++ // - see EntitySkeletonAbstract melee goal ++ void setAggressive(boolean flag); default void setChargingAttack(boolean charging) { setAggressive(charging); }; // Paper + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java +index 3bb39dae11847bea1330ef68f53c90309fd2a095..bba2e3fba5b225e90744e78df085b3c318a029ce 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java +@@ -5,7 +5,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Drowned; + import org.bukkit.entity.EntityType; + +-public class CraftDrowned extends CraftZombie implements Drowned { ++public class CraftDrowned extends CraftZombie implements Drowned, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + public CraftDrowned(CraftServer server, EntityDrowned entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java +index 0dabb012b37b6dda58368765b220b7d0aaf8e2d4..3763fb13920c98bb7cd250883ec89cdd1805dbd6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java +@@ -5,7 +5,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.Illusioner; + +-public class CraftIllusioner extends CraftSpellcaster implements Illusioner { ++public class CraftIllusioner extends CraftSpellcaster implements Illusioner, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + public CraftIllusioner(CraftServer server, EntityIllagerIllusioner entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +index 6dcf196fd83f2175a5d34c8d138d923c32ddb899..818034c62893a71808e3af0aa33393605611acdd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit.entity; + ++import com.destroystokyo.paper.entity.CraftRangedEntity; + import com.google.common.base.Preconditions; + import net.minecraft.world.entity.animal.horse.EntityLlama; + import org.bukkit.craftbukkit.CraftServer; +@@ -10,7 +11,7 @@ import org.bukkit.entity.Llama; + import org.bukkit.entity.Llama.Color; + import org.bukkit.inventory.LlamaInventory; + +-public class CraftLlama extends CraftChestedHorse implements Llama { ++public class CraftLlama extends CraftChestedHorse implements Llama, CraftRangedEntity { // Paper + + public CraftLlama(CraftServer server, EntityLlama entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +index a224ebf16834fa10e169fa3239d89b1e60673dbb..b3b5d3a7f8ed522680f764cfd20d1e5c69627804 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +@@ -5,7 +5,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.Piglin; + +-public class CraftPiglin extends CraftPiglinAbstract implements Piglin { ++public class CraftPiglin extends CraftPiglinAbstract implements Piglin, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + public CraftPiglin(CraftServer server, EntityPiglin entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java +index 72ac9eae71028a40541f949d617ce326c00c6369..87eeb5b632b581dca7613973bc9a25f152839a33 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java +@@ -7,7 +7,7 @@ import org.bukkit.entity.EntityType; + import org.bukkit.entity.Pillager; + import org.bukkit.inventory.Inventory; + +-public class CraftPillager extends CraftIllager implements Pillager { ++public class CraftPillager extends CraftIllager implements Pillager, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + public CraftPillager(CraftServer server, EntityPillager entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +index cebb90fd4a84c0a97d7493a6923d16d0c4215f5e..c2acfa2cc27a187154e17b7f45908682b41b52af 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +@@ -6,7 +6,7 @@ import org.bukkit.entity.EntityType; + import org.bukkit.entity.Skeleton; + import org.bukkit.entity.Skeleton.SkeletonType; + +-public class CraftSkeleton extends CraftMonster implements Skeleton { ++public class CraftSkeleton extends CraftMonster implements Skeleton, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + public CraftSkeleton(CraftServer server, EntitySkeletonAbstract 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 5720bdf64eadec1ebe1a2253e2b537ca299ffa6d..a262cf88eefca2767eb6e5da856626be34352ccd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +@@ -5,7 +5,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.Snowman; + +-public class CraftSnowman extends CraftGolem implements Snowman { ++public class CraftSnowman extends CraftGolem implements Snowman, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + public CraftSnowman(CraftServer server, EntitySnowman entity) { + super(server, entity); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +index 796bd26d41b52941c38d81411688116af7053535..9cc34cdb43596eff34625045f884b93da3f27ab6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +@@ -5,7 +5,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.Witch; + +-public class CraftWitch extends CraftRaider implements Witch { ++public class CraftWitch extends CraftRaider implements Witch, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + public CraftWitch(CraftServer server, EntityWitch entity) { + super(server, entity); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +index 7150702d8dac0d9db44661b1b17f520302988b45..1d94aeec37dcb9758d88ef25a5cad1333bbfbf6c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +@@ -7,7 +7,7 @@ import org.bukkit.craftbukkit.boss.CraftBossBar; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.Wither; + +-public class CraftWither extends CraftMonster implements Wither { ++public class CraftWither extends CraftMonster implements Wither, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + + private BossBar bossBar; + diff --git a/patches/server-unmapped/0001/0233-Add-config-to-disable-ender-dragon-legacy-check.patch b/patches/server-unmapped/0001/0233-Add-config-to-disable-ender-dragon-legacy-check.patch new file mode 100644 index 0000000000..f8cb33eba2 --- /dev/null +++ b/patches/server-unmapped/0001/0233-Add-config-to-disable-ender-dragon-legacy-check.patch @@ -0,0 +1,44 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3e4bd1d6718d3ad2498fe9bd72eaac45044ecb77..4813f62d1e382d5ac6971b2244df3f13c80d1950 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -374,4 +374,9 @@ public class PaperWorldConfig { + private void shieldBlockingDelay() { + shieldBlockingDelay = getInt("game-mechanics.shield-blocking-delay", 5); + } ++ ++ public boolean scanForLegacyEnderDragon = true; ++ private void scanForLegacyEnderDragon() { ++ scanForLegacyEnderDragon = getBoolean("game-mechanics.scan-for-legacy-ender-dragon", true); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +index ab942253a99234565f83ddcba965e25880877f57..67077351e6e85ade753b960704743fa0fd9a158d 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +@@ -73,7 +73,7 @@ public class EnderDragonBattle { + private boolean dragonKilled; + private boolean previouslyKilled; + public UUID dragonUUID; +- private boolean n; ++ private boolean n; private void setScanForLegacyFight(boolean scanForLegacyFight) { this.n = scanForLegacyFight; } private boolean scanForLegacyFight() { return this.n; } // Paper - OBFHELPER + public BlockPosition exitPortalLocation; + public EnumDragonRespawn respawnPhase; + private int q; +@@ -83,6 +83,10 @@ public class EnderDragonBattle { + this.bossBattle = (BossBattleServer) (new BossBattleServer(new ChatMessage("entity.minecraft.ender_dragon"), BossBattle.BarColor.PINK, BossBattle.BarStyle.PROGRESS)).setPlayMusic(true).setCreateFog(true); + this.gateways = Lists.newArrayList(); + this.n = true; ++ // Paper start ++ setScanForLegacyFight(worldserver.paperConfig.scanForLegacyEnderDragon); ++ if (!scanForLegacyFight()) dragonKilled = true; ++ // Paper end + this.world = worldserver; + if (nbttagcompound.hasKeyOfType("DragonKilled", 99)) { + if (nbttagcompound.b("Dragon")) { diff --git a/patches/server-unmapped/0001/0234-Implement-World.getEntity-UUID-API.patch b/patches/server-unmapped/0001/0234-Implement-World.getEntity-UUID-API.patch new file mode 100644 index 0000000000..b4f7eca0b2 --- /dev/null +++ b/patches/server-unmapped/0001/0234-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 36c9ee703660d0ef558481ef0fea0984502bc14e..945ea6022d89bfb6ee4429233909f8294141fbb6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1304,6 +1304,15 @@ public class CraftWorld implements World { + return list; + } + ++ // Paper start - getEntity by UUID API ++ @Override ++ public Entity getEntity(UUID uuid) { ++ Validate.notNull(uuid, "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-unmapped/0001/0235-InventoryCloseEvent-Reason-API.patch b/patches/server-unmapped/0001/0235-InventoryCloseEvent-Reason-API.patch new file mode 100644 index 0000000000..d669752040 --- /dev/null +++ b/patches/server-unmapped/0001/0235-InventoryCloseEvent-Reason-API.patch @@ -0,0 +1,227 @@ +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/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 3d517ab98da5fd56101e97b5678f7180839269f8..7b07d17f8ae425418ff3cafdfd30d72a5bc0fe16 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -546,7 +546,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + // Paper end + if (!this.world.isClientSide && !this.activeContainer.canUse(this)) { +- this.closeInventory(); ++ this.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper + this.activeContainer = this.defaultContainer; + } + +@@ -719,7 +719,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + // SPIGOT-943 - only call if they have an inventory open + if (this.activeContainer != this.defaultContainer) { +- this.closeInventory(); ++ this.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DEATH); // Paper + } + + net.kyori.adventure.text.Component deathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure +@@ -1284,7 +1284,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + return OptionalInt.empty(); + } else { + if (this.activeContainer != this.defaultContainer) { +- this.closeInventory(); ++ this.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper + } + + this.nextContainerCounter(); +@@ -1344,7 +1344,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + // CraftBukkit end + if (this.activeContainer != this.defaultContainer) { +- this.closeInventory(); ++ this.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper + } + + // this.nextContainerCounter(); // CraftBukkit - moved up +@@ -1408,7 +1408,12 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + @Override + public void closeInventory() { +- CraftEventFactory.handleInventoryCloseEvent(this); // CraftBukkit ++ // Paper start ++ closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); ++ } ++ public void closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit ++ // Paper end + this.playerConnection.sendPacket(new PacketPlayOutCloseWindow(this.activeContainer.windowId)); + this.o(); + } +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 96d67fc3cbe61c1fb6e639b1838b2fd198c3f3d5..c7e54920b40bdf049f2192310bfdb9d1749580d0 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1125,7 +1125,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + for (TileEntity tileentity : chunk.getTileEntities().values()) { + if (tileentity instanceof net.minecraft.world.IInventory) { + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.IInventory) tileentity).getViewers())) { +- h.closeInventory(); ++ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper + } + } + } +@@ -1183,7 +1183,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + // Spigot Start + if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder) { + 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 + } + } + // Spigot End +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index d4862a3a7f523c13c452e7b67072261556162437..5c637fc653482e931fa6d0f5d0394d0b923b5ff8 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -190,6 +190,7 @@ import org.bukkit.event.inventory.ClickType; + import org.bukkit.event.inventory.CraftItemEvent; + import org.bukkit.event.inventory.InventoryAction; + import org.bukkit.event.inventory.InventoryClickEvent; ++import org.bukkit.event.inventory.InventoryCloseEvent; // Paper + import org.bukkit.event.inventory.InventoryCreativeEvent; + import org.bukkit.event.inventory.InventoryType.SlotType; + import org.bukkit.event.inventory.SmithItemEvent; +@@ -2310,10 +2311,15 @@ public class PlayerConnection implements PacketListenerPlayIn { + + @Override + public void a(PacketPlayInCloseWindow packetplayinclosewindow) { ++ // Paper start ++ handleContainerClose(packetplayinclosewindow, InventoryCloseEvent.Reason.PLAYER); ++ } ++ public void handleContainerClose(PacketPlayInCloseWindow packetplayinclosewindow, InventoryCloseEvent.Reason reason) { ++ // Paper end + PlayerConnectionUtils.ensureMainThread(packetplayinclosewindow, this, this.player.getWorldServer()); + + if (this.player.isFrozen()) return; // CraftBukkit +- CraftEventFactory.handleInventoryCloseEvent(this.player); // CraftBukkit ++ CraftEventFactory.handleInventoryCloseEvent(this.player, reason); // CraftBukkit // Paper + + this.player.o(); + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index ae877ea38a63ef8d0bd9855e9b9279475bb6c465..95cadb09b5a154d7dfe8144fab6c403547672287 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -494,7 +494,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.activeContainer != entityplayer.defaultContainer) { +- entityplayer.closeInventory(); ++ entityplayer.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getName()))); +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 3c49d7acd4ad0717886adf6c469e8a49a58e859b..b6effe1037f3ae59e6faa5f5d039b6ad54bca5d4 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -252,7 +252,7 @@ public abstract class EntityHuman extends EntityLiving { + this.et(); + super.tick(); + if (!this.world.isClientSide && this.activeContainer != null && !this.activeContainer.canUse(this)) { +- this.closeInventory(); ++ this.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper + this.activeContainer = this.defaultContainer; + } + +@@ -447,6 +447,13 @@ public abstract class EntityHuman extends EntityLiving { + return 20; + } + ++ // Paper start - unused code, but to keep signatures aligned ++ public void closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ closeInventory(); ++ this.activeContainer = this.defaultContainer; ++ } ++ // Paper end ++ + public void closeInventory() { + this.activeContainer = this.defaultContainer; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index db7ad5a94d449f58a5749115776e61f448ff2f52..e8f8a07f256e01c5792199bf47f3cc1f0f3d1610 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -373,7 +373,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + if (((EntityPlayer) getHandle()).playerConnection == null) return; + if (getHandle().activeContainer != getHandle().defaultContainer) { + // fire INVENTORY_CLOSE if one already open +- ((EntityPlayer) getHandle()).playerConnection.a(new PacketPlayInCloseWindow(getHandle().activeContainer.windowId)); ++ ((EntityPlayer) getHandle()).playerConnection.handleContainerClose(new PacketPlayInCloseWindow(getHandle().activeContainer.windowId), org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper + } + EntityPlayer player = (EntityPlayer) getHandle(); + Container container; +@@ -443,8 +443,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + + @Override + public void closeInventory() { +- getHandle().closeInventory(); ++ // Paper start ++ getHandle().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLUGIN); + } ++ public void closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ getHandle().closeInventory(reason); ++ } ++ // Paper end + + @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 871c0e0b0c6df68c0f8c87828a01fe006d0646fb..32228b4eddaadabbae46ebbc5eb3404acf73fb29 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -900,7 +900,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + // Close any foreign inventory + if (getHandle().activeContainer != getHandle().defaultContainer) { +- getHandle().closeInventory(); ++ getHandle().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); // Paper + } + + // 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 791a4490c25f88aa4525c98e794ff3b2bfe194ed..db8dc8e5dda304a3ccd81dc0c781476d5946a1ba 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1166,7 +1166,7 @@ public class CraftEventFactory { + + public static Container callInventoryOpenEvent(EntityPlayer player, Container container, boolean cancelled) { + if (player.activeContainer != player.defaultContainer) { // fire INVENTORY_CLOSE if one already open +- player.playerConnection.a(new PacketPlayInCloseWindow(player.activeContainer.windowId)); ++ player.playerConnection.handleContainerClose(new PacketPlayInCloseWindow(player.activeContainer.windowId), InventoryCloseEvent.Reason.OPEN_NEW); // Paper + } + + CraftServer server = player.world.getServer(); +@@ -1331,8 +1331,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(EntityHuman human) { +- InventoryCloseEvent event = new InventoryCloseEvent(human.activeContainer.getBukkitView()); ++ handleInventoryCloseEvent(human, org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); ++ } ++ public static void handleInventoryCloseEvent(EntityHuman human, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // Paper end ++ InventoryCloseEvent event = new InventoryCloseEvent(human.activeContainer.getBukkitView(), reason); // Paper + human.world.getServer().getPluginManager().callEvent(event); + human.activeContainer.transferTo(human.defaultContainer, human.getBukkitEntity()); + } diff --git a/patches/server-unmapped/0001/0236-Vex-getSummoner-API.patch b/patches/server-unmapped/0001/0236-Vex-getSummoner-API.patch new file mode 100644 index 0000000000..c70d5e9bb3 --- /dev/null +++ b/patches/server-unmapped/0001/0236-Vex-getSummoner-API.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 Jul 2018 15:30:22 -0400 +Subject: [PATCH] Vex#getSummoner API + +Get's the NPC that summoned this Vex + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityVex.java b/src/main/java/net/minecraft/world/entity/monster/EntityVex.java +index 3acf36a500424808cd91dc607df1ffbe23720c16..3b74ade60b3b0ae0e908866cb4ac11acd75620ff 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityVex.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityVex.java +@@ -124,6 +124,7 @@ public class EntityVex extends EntityMonster { + + } + ++ public EntityInsentient getOwner() { return eK(); } // Paper - OBFHELPER + public EntityInsentient eK() { + return this.c; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +index ccaccffbf5eb18108760d1da09c4c1b2f33ebc42..962d6017f6acc47ebe4b754ccd9b97a1fc97cc58 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +@@ -16,6 +16,13 @@ public class CraftVex extends CraftMonster implements Vex { + return (EntityVex) super.getHandle(); + } + ++ // Paper start ++ public org.bukkit.entity.Mob getSummoner() { ++ net.minecraft.world.entity.EntityInsentient owner = getHandle().getOwner(); ++ return owner != null ? (org.bukkit.entity.Mob) owner.getBukkitEntity() : null; ++ } ++ // Paper end ++ + @Override + public String toString() { + return "CraftVex"; diff --git a/patches/server-unmapped/0001/0237-Refresh-player-inventory-when-cancelling-PlayerInter.patch b/patches/server-unmapped/0001/0237-Refresh-player-inventory-when-cancelling-PlayerInter.patch new file mode 100644 index 0000000000..619604e5af --- /dev/null +++ b/patches/server-unmapped/0001/0237-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/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 5c637fc653482e931fa6d0f5d0394d0b923b5ff8..19763a27ad54866b7d1f6e2cfccd6bbe6e54637e 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -2223,6 +2223,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + + if (event.isCancelled()) { ++ this.player.updateInventory(this.player.activeContainer); // Paper - Refresh player inventory + return; + } + // CraftBukkit end diff --git a/patches/server-unmapped/0001/0238-Don-t-change-the-Entity-Random-seed-for-squids.patch b/patches/server-unmapped/0001/0238-Don-t-change-the-Entity-Random-seed-for-squids.patch new file mode 100644 index 0000000000..7640438b07 --- /dev/null +++ b/patches/server-unmapped/0001/0238-Don-t-change-the-Entity-Random-seed-for-squids.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 19 Jul 2018 01:05:00 -0400 +Subject: [PATCH] Don't change the Entity Random seed for squids + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntitySquid.java b/src/main/java/net/minecraft/world/entity/animal/EntitySquid.java +index 7ce5e2597b34d3a4d2a79d73c15e893c064fc88c..1f5f3e0d209426b97e32b82dd15176b800f85816 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntitySquid.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntitySquid.java +@@ -48,7 +48,7 @@ public class EntitySquid extends EntityWaterAnimal { + + public EntitySquid(EntityTypes entitytypes, World world) { + super(entitytypes, world); +- this.random.setSeed((long) this.getId()); ++ //this.random.setSeed((long) this.getId()); // Paper + this.bu = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; + } + diff --git a/patches/server-unmapped/0001/0239-Re-add-vanilla-entity-warnings-for-duplicates.patch b/patches/server-unmapped/0001/0239-Re-add-vanilla-entity-warnings-for-duplicates.patch new file mode 100644 index 0000000000..9a588302c8 --- /dev/null +++ b/patches/server-unmapped/0001/0239-Re-add-vanilla-entity-warnings-for-duplicates.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 19 Jul 2018 01:08:05 -0400 +Subject: [PATCH] Re-add vanilla entity warnings for duplicates + +These are a critical sign that somethin went wrong, and you've lost some data.... + +We should kind of know about these things you know. + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index c7e54920b40bdf049f2192310bfdb9d1749580d0..9af581339884d99709242735ad655d90faf7224a 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1077,7 +1077,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + if (entity1 == null) { + return false; + } else { +- // WorldServer.LOGGER.warn("Trying to add entity with duplicated UUID {}. Existing {}#{}, new: {}#{}", uuid, EntityTypes.getName(entity1.getEntityType()), entity1.getId(), EntityTypes.getName(entity.getEntityType()), entity.getId()); // CraftBukkit ++ WorldServer.LOGGER.warn("Trying to add entity with duplicated UUID {}. Existing {}#{}, new: {}#{}", uuid, EntityTypes.getName(entity1.getEntityType()), entity1.getId(), EntityTypes.getName(entity.getEntityType()), entity.getId()); // CraftBukkit // Paper + return true; + } + } diff --git a/patches/server-unmapped/0001/0240-Avoid-item-merge-if-stack-size-above-max-stack-size.patch b/patches/server-unmapped/0001/0240-Avoid-item-merge-if-stack-size-above-max-stack-size.patch new file mode 100644 index 0000000000..aded79d749 --- /dev/null +++ b/patches/server-unmapped/0001/0240-Avoid-item-merge-if-stack-size-above-max-stack-size.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Manrique +Date: Mon, 16 Jul 2018 12:42:20 +0200 +Subject: [PATCH] Avoid item merge if stack size above max stack size + + +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityItem.java b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +index de11fd9772f30ac72c3ca52ec4efc3fef4091425..4cfe3475fa913cd46116f13ea8ed9caf5372a41a 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityItem.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +@@ -209,6 +209,10 @@ public class EntityItem extends Entity { + + private void mergeNearby() { + if (this.z()) { ++ // Paper start - avoid item merge if stack size above max stack size ++ ItemStack stack = getItemStack(); ++ if (stack.getCount() >= stack.getMaxStackSize()) return; ++ // Paper end + // Spigot start + double radius = world.spigotConfig.itemMerge; + List list = this.world.a(EntityItem.class, this.getBoundingBox().grow(radius, radius, radius), (entityitem) -> { diff --git a/patches/server-unmapped/0001/0241-Use-asynchronous-Log4j-2-loggers.patch b/patches/server-unmapped/0001/0241-Use-asynchronous-Log4j-2-loggers.patch new file mode 100644 index 0000000000..10fdc401da --- /dev/null +++ b/patches/server-unmapped/0001/0241-Use-asynchronous-Log4j-2-loggers.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Tue, 17 Jul 2018 16:42:17 +0200 +Subject: [PATCH] Use asynchronous Log4j 2 loggers + + +diff --git a/pom.xml b/pom.xml +index 3ebdf97dd88e9461331434da4a8cd02bd25148ca..325dc0a46666d5e0cd050fd1fb793c08866c2a43 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -93,6 +93,13 @@ + log4j-iostreams + compile + ++ ++ ++ com.lmax ++ disruptor ++ 3.4.2 ++ runtime ++ + + org.ow2.asm + asm +diff --git a/src/main/java/com/destroystokyo/paper/log/LogFullPolicy.java b/src/main/java/com/destroystokyo/paper/log/LogFullPolicy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..db652a1f7abc80bc751fd94925abaec58ab1a563 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/log/LogFullPolicy.java +@@ -0,0 +1,17 @@ ++package com.destroystokyo.paper.log; ++ ++import org.apache.logging.log4j.Level; ++import org.apache.logging.log4j.core.async.AsyncQueueFullPolicy; ++import org.apache.logging.log4j.core.async.EventRoute; ++ ++public final class LogFullPolicy implements AsyncQueueFullPolicy { ++ ++ /* ++ * Prevents log calls being logged out of order when the log queue is full. ++ */ ++ ++ @Override ++ public EventRoute getRoute(final long backgroundThreadId, final Level level) { ++ return EventRoute.ENQUEUE; ++ } ++} +diff --git a/src/main/resources/log4j2.component.properties b/src/main/resources/log4j2.component.properties +index 0694b21465fb9e4164e71862ff24b62241b191f2..30efeb5faf8e7faccf1b252fa0ed6a9fc31c40a7 100644 +--- a/src/main/resources/log4j2.component.properties ++++ b/src/main/resources/log4j2.component.properties +@@ -1 +1,3 @@ ++Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector ++log4j2.AsyncQueueFullPolicy="com.destroystokyo.paper.log.LogFullPolicy" + log4j.skipJansi=true diff --git a/patches/server-unmapped/0001/0242-add-more-information-to-Entity.toString.patch b/patches/server-unmapped/0001/0242-add-more-information-to-Entity.toString.patch new file mode 100644 index 0000000000..a507bf2bac --- /dev/null +++ b/patches/server-unmapped/0001/0242-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 828f238e8f3b1a98310532e07c72161582c91c5b..bd9cd050c72792c3d5a2094991b21e3a998b2cd9 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2522,7 +2522,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public String toString() { +- return String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]", this.getClass().getSimpleName(), this.getDisplayName().getString(), this.id, this.world == null ? "~NULL~" : this.world.toString(), this.locX(), this.locY(), this.locZ()); ++ return String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cx=%d, cz=%d, tl=%d, v=%b, d=%b]", new Object[] { this.getClass().getSimpleName(), this.getDisplayName().getString(), Integer.valueOf(this.id), this.uniqueID.toString(), this.world == null ? "~NULL~" : this.world.toString(), Double.valueOf(this.locX()), Double.valueOf(this.locY()), Double.valueOf(this.locZ()), chunkX, chunkZ, this.ticksLived, this.valid, this.dead}); // Paper - add more information + } + + public boolean isInvulnerable(DamageSource damagesource) { diff --git a/patches/server-unmapped/0001/0243-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch b/patches/server-unmapped/0001/0243-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch new file mode 100644 index 0000000000..4e789f8e9d --- /dev/null +++ b/patches/server-unmapped/0001/0243-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch @@ -0,0 +1,131 @@ +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 + +Add -Ddebug.entities=true to your JVM flags to gain more information + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index c6b9b02e6d31bebb3f8c0cadd68e4b5c47fab090..c4dd2bac48bb93117925b35dcd753d0fbb22e3cf 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -1139,6 +1139,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } else { + PlayerChunkMap.EntityTracker playerchunkmap_entitytracker = new PlayerChunkMap.EntityTracker(entity, i, j, entitytypes.isDeltaTracking()); + ++ entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker + this.trackedEntities.put(entity.getId(), playerchunkmap_entitytracker); + playerchunkmap_entitytracker.track(this.world.getPlayers()); + if (entity instanceof EntityPlayer) { +@@ -1180,7 +1181,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + if (playerchunkmap_entitytracker1 != null) { + playerchunkmap_entitytracker1.a(); + } +- ++ entity.tracker = null; // Paper - We're no longer tracked + } + + protected void g() { +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 9af581339884d99709242735ad655d90faf7224a..14321bc6ecc5ca70e71c1eef9578091822aa94cd 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -197,6 +197,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { + public final Convertable.ConversionSession convertable; + public final UUID uuid; + public boolean hasPhysicsEvent = true; // Paper ++ private static Throwable getAddToWorldStackTrace(Entity entity) { ++ return new Throwable(entity + " Added to world at " + new java.util.Date()); ++ } + + @Override public Chunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI + return this.chunkProvider.getChunkAt(x, z, false); +@@ -1038,8 +1041,28 @@ public class WorldServer extends World implements GeneratorAccessSeed { + // CraftBukkit start + private boolean addEntity0(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { + org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot +- if (entity.valid) { MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); return true; } // Paper ++ // Paper start ++ if (entity.valid) { ++ MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); ++ ++ if (DEBUG_ENTITIES) { ++ Throwable thr = entity.addedToWorldStack; ++ if (thr == null) { ++ MinecraftServer.LOGGER.error("Double add entity has no add stacktrace"); ++ } else { ++ MinecraftServer.LOGGER.error("Double add stacktrace: ", thr); ++ } ++ } ++ return true; ++ } ++ // Paper end + if (entity.dead) { ++ // Paper start ++ if (DEBUG_ENTITIES) { ++ new Throwable("Tried to add entity " + entity + " but it was marked as removed already").printStackTrace(); // CraftBukkit ++ getAddToWorldStackTrace(entity).printStackTrace(); ++ } ++ // Paper end + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getName(entity.getEntityType())); // CraftBukkit + return false; + } else if (this.isUUIDTaken(entity)) { +@@ -1237,7 +1260,24 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + } + +- this.entitiesByUUID.put(entity.getUniqueID(), entity); ++ if (DEBUG_ENTITIES) { ++ entity.addedToWorldStack = getAddToWorldStackTrace(entity); ++ } ++ ++ Entity old = this.entitiesByUUID.put(entity.getUniqueID(), entity); ++ if (old != null && old.getId() != entity.getId() && old.valid) { ++ Logger logger = LogManager.getLogger(); ++ logger.error("Overwrote an existing entity " + old + " with " + entity); ++ if (DEBUG_ENTITIES) { ++ if (old.addedToWorldStack != null) { ++ old.addedToWorldStack.printStackTrace(); ++ } else { ++ logger.error("Oddly, the old entity was not added to the world in the normal way. Plugins?"); ++ } ++ entity.addedToWorldStack.printStackTrace(); ++ } ++ } ++ + this.getChunkProvider().addEntity(entity); + // CraftBukkit start - SPIGOT-5278 + if (entity instanceof EntityDrowned) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index bd9cd050c72792c3d5a2094991b21e3a998b2cd9..c88eea18e2e219f242c53ffb4e28cfc6d7bf318a 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -48,6 +48,7 @@ import net.minecraft.resources.MinecraftKey; + import net.minecraft.resources.ResourceKey; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.PlayerChunkMap; + import net.minecraft.server.level.TicketType; + import net.minecraft.server.level.WorldServer; + import net.minecraft.sounds.SoundCategory; +@@ -161,6 +162,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper + private CraftEntity bukkitEntity; + ++ PlayerChunkMap.EntityTracker tracker; // Paper ++ public Throwable addedToWorldStack; // Paper - entity debug + public CraftEntity getBukkitEntity() { + if (bukkitEntity == null) { + bukkitEntity = CraftEntity.getEntity(world.getServer(), this); +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 118acde8ec2cb613e03b891b037cc8bb06548682..384855edcc9c31a554c5f603ca53b373b3c9095a 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -121,6 +121,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public boolean pvpMode; + public boolean keepSpawnInMemory = true; + public org.bukkit.generator.ChunkGenerator generator; ++ public static final boolean DEBUG_ENTITIES = Boolean.getBoolean("debug.entities"); // Paper + + public boolean captureBlockStates = false; + public boolean captureTreeGeneration = false; diff --git a/patches/server-unmapped/0001/0244-EnderDragon-Events.patch b/patches/server-unmapped/0001/0244-EnderDragon-Events.patch new file mode 100644 index 0000000000..2ac7798d52 --- /dev/null +++ b/patches/server-unmapped/0001/0244-EnderDragon-Events.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 21 Jul 2018 01:51:27 -0500 +Subject: [PATCH] EnderDragon Events + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerLandedFlame.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerLandedFlame.java +index 5adbd9fe858aad9c775a10254eb53b34719a9bd6..91de4e6c1d478e001c8672d34b4ffe57f6cba0a6 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerLandedFlame.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerLandedFlame.java +@@ -80,7 +80,11 @@ public class DragonControllerLandedFlame extends AbstractDragonControllerLanded + this.d.setDuration(200); + this.d.setParticle(Particles.DRAGON_BREATH); + this.d.addEffect(new MobEffect(MobEffects.HARM)); ++ if (new com.destroystokyo.paper.event.entity.EnderDragonFlameEvent((org.bukkit.entity.EnderDragon) this.a.getBukkitEntity(), (org.bukkit.entity.AreaEffectCloud) this.d.getBukkitEntity()).callEvent()) { // Paper + this.a.world.addEntity(this.d); ++ } else { ++ this.removeAreaEffect(); ++ } + } + + } +@@ -91,8 +95,8 @@ public class DragonControllerLandedFlame extends AbstractDragonControllerLanded + ++this.c; + } + +- @Override +- public void e() { ++ public final void removeAreaEffect() { this.e(); } // Paper - OBFHELPER ++ @Override public void e() { + if (this.d != null) { + this.d.die(); + this.d = null; +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerStrafe.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerStrafe.java +index db3cef26c8d5cdf740bb151a5525d8740a0e8bbd..1a5d5d39d3090acc3914e40e8d30c4a09789dbc9 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerStrafe.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonControllerStrafe.java +@@ -81,7 +81,9 @@ public class DragonControllerStrafe extends AbstractDragonController { + EntityDragonFireball entitydragonfireball = new EntityDragonFireball(this.a.world, this.a, d9, d10, d11); + + entitydragonfireball.setPositionRotation(d6, d7, d8, 0.0F, 0.0F); ++ if (new com.destroystokyo.paper.event.entity.EnderDragonShootFireballEvent((org.bukkit.entity.EnderDragon) a.getBukkitEntity(), (org.bukkit.entity.DragonFireball) entitydragonfireball.getBukkitEntity()).callEvent()) // Paper + this.a.world.addEntity(entitydragonfireball); ++ else entitydragonfireball.die(); // Paper + this.c = 0; + if (this.d != null) { + while (!this.d.c()) { +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityDragonFireball.java b/src/main/java/net/minecraft/world/entity/projectile/EntityDragonFireball.java +index 59b5484731a5f71005c3efa56cbe40012d9641b5..27853f510e15e40c66da2cb4905c43f5e8f99d3d 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityDragonFireball.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityDragonFireball.java +@@ -58,8 +58,10 @@ public class EntityDragonFireball extends EntityFireball { + } + } + ++ if (new com.destroystokyo.paper.event.entity.EnderDragonFireballHitEvent((org.bukkit.entity.DragonFireball) this.getBukkitEntity(), list.stream().map(EntityLiving::getBukkitLivingEntity).collect(java.util.stream.Collectors.toList()), (org.bukkit.entity.AreaEffectCloud) entityareaeffectcloud.getBukkitEntity()).callEvent()) { // Paper + this.world.triggerEffect(2006, this.getChunkCoordinates(), this.isSilent() ? -1 : 1); + this.world.addEntity(entityareaeffectcloud); ++ } else entityareaeffectcloud.die(); // Paper + this.die(); + } + diff --git a/patches/server-unmapped/0001/0245-PlayerElytraBoostEvent.patch b/patches/server-unmapped/0001/0245-PlayerElytraBoostEvent.patch new file mode 100644 index 0000000000..684680a7ed --- /dev/null +++ b/patches/server-unmapped/0001/0245-PlayerElytraBoostEvent.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 21 Jul 2018 01:59:59 -0500 +Subject: [PATCH] PlayerElytraBoostEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ItemFireworks.java b/src/main/java/net/minecraft/world/item/ItemFireworks.java +index a2950faa48021782f10db0673d12d178443f7ccc..79e9be800385b94c4493bd8970620d76bfbd65ae 100644 +--- a/src/main/java/net/minecraft/world/item/ItemFireworks.java ++++ b/src/main/java/net/minecraft/world/item/ItemFireworks.java +@@ -45,11 +45,16 @@ public class ItemFireworks extends Item { + // Paper start + final EntityFireworks entityfireworks = new EntityFireworks(world, itemstack, entityhuman); + entityfireworks.spawningEntity = entityhuman.getUniqueID(); +- world.addEntity(entityfireworks); +- // Paper end +- if (!entityhuman.abilities.canInstantlyBuild) { +- itemstack.subtract(1); ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Firework) entityfireworks.getBukkitEntity()); ++ if (event.callEvent() && world.addEntity(entityfireworks)) { ++ if (event.shouldConsume() && !entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } else ((EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); ++ } else if (entityhuman instanceof EntityPlayer) { ++ ((EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); + } ++ // Paper end + } + + return InteractionResultWrapper.a(entityhuman.b(enumhand), world.s_()); diff --git a/patches/server-unmapped/0001/0246-Improve-BlockPosition-inlining.patch b/patches/server-unmapped/0001/0246-Improve-BlockPosition-inlining.patch new file mode 100644 index 0000000000..9d4fb270ac --- /dev/null +++ b/patches/server-unmapped/0001/0246-Improve-BlockPosition-inlining.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Techcable +Date: Wed, 30 Nov 2016 20:56:58 -0600 +Subject: [PATCH] Improve BlockPosition inlining + +Normally the JVM can inline virtual getters by having two sets of code, one is the 'optimized' code and the other is the 'deoptimized' code. +If a single type is used 99% of the time, then its worth it to inline, and to revert to 'deoptimized' the 1% of the time we encounter other types. +But if two types are encountered commonly, then the JVM can't inline them both, and the call overhead remains. + +This scenario also occurs with BlockPos and MutableBlockPos. +The variables in BlockPos are final, so MutableBlockPos can't modify them. +MutableBlockPos fixes this by adding custom mutable variables, and overriding the getters to access them. + +This approach with utility methods that operate on MutableBlockPos and BlockPos. +Specific examples are BlockPosition.up(), and World.isValidLocation(). +It makes these simple methods much slower than they need to be. + +This should result in an across the board speedup in anything that accesses blocks or does logic with positions. + +This is based upon conclusions drawn from inspecting the assenmbly generated bythe JIT compiler on my microbenchmarks. +They had 'callq' (invoke) instead of 'mov' (get from memory) instructions. + +diff --git a/src/main/java/net/minecraft/core/BaseBlockPosition.java b/src/main/java/net/minecraft/core/BaseBlockPosition.java +index 4b56683336fdab06804efdc8ca1f7c130b77291f..a44bcdb053877a6281e566ffe03ef72ffd50ca08 100644 +--- a/src/main/java/net/minecraft/core/BaseBlockPosition.java ++++ b/src/main/java/net/minecraft/core/BaseBlockPosition.java +@@ -41,7 +41,7 @@ public class BaseBlockPosition implements Comparable { + this(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2)); + } + +- public boolean equals(Object object) { ++ public final boolean equals(Object object) { // Paper + if (this == object) { + return true; + } else if (!(object instanceof BaseBlockPosition)) { +@@ -53,7 +53,7 @@ public class BaseBlockPosition implements Comparable { + } + } + +- public int hashCode() { ++ public final int hashCode() { // Paper + return (this.getY() + this.getZ() * 31) * 31 + this.getX(); + } + +@@ -61,15 +61,15 @@ public class BaseBlockPosition implements Comparable { + return this.getY() == baseblockposition.getY() ? (this.getZ() == baseblockposition.getZ() ? this.getX() - baseblockposition.getX() : this.getZ() - baseblockposition.getZ()) : this.getY() - baseblockposition.getY(); + } + +- public int getX() { ++ public final int getX() { // Paper + return this.a; + } + +- public int getY() { ++ public final int getY() { // Paper + return this.b; + } + +- public int getZ() { ++ public final int getZ() { // Paper + return this.e; + } + +diff --git a/src/main/java/net/minecraft/core/BlockPosition.java b/src/main/java/net/minecraft/core/BlockPosition.java +index 9fb6db18c5c1f39b5a564c0f5f70498825defa97..370b2c4460d6b52b5ef7da89f5ebf7ef50deb582 100644 +--- a/src/main/java/net/minecraft/core/BlockPosition.java ++++ b/src/main/java/net/minecraft/core/BlockPosition.java +@@ -88,6 +88,7 @@ public class BlockPosition extends BaseBlockPosition { + return a(this.getX(), this.getY(), this.getZ()); + } + ++ public static long asLong(int x, int y, int z) { return a(x, y, z); } // Paper - OBFHELPER + public static long a(int i, int j, int k) { + long l = 0L; + diff --git a/patches/server-unmapped/0001/0247-Optimize-RegistryID.c.patch b/patches/server-unmapped/0001/0247-Optimize-RegistryID.c.patch new file mode 100644 index 0000000000..028620de8b --- /dev/null +++ b/patches/server-unmapped/0001/0247-Optimize-RegistryID.c.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 23 Jul 2018 13:08:19 -0400 +Subject: [PATCH] Optimize RegistryID.c() + +This is a frequent hotspot for world loading/saving. + +diff --git a/src/main/java/net/minecraft/util/RegistryID.java b/src/main/java/net/minecraft/util/RegistryID.java +index 6150f7a5c5004ac79414ab22dbaa3439dc8afdb4..a59dbfa01743137702b122f73c778452e63115b5 100644 +--- a/src/main/java/net/minecraft/util/RegistryID.java ++++ b/src/main/java/net/minecraft/util/RegistryID.java +@@ -15,12 +15,14 @@ public class RegistryID implements Registry { + private K[] d; + private int e; + private int f; ++ private java.util.BitSet usedIds; // Paper + + public RegistryID(int i) { + i = (int) ((float) i / 0.8F); + this.b = (K[]) (new Object[i]); // Paper - decompile fix + this.c = new int[i]; + this.d = (K[]) (new Object[i]); // Paper - decompile fix ++ this.usedIds = new java.util.BitSet(); // Paper + } + + // Paper start - decompile fix +@@ -52,9 +54,14 @@ public class RegistryID implements Registry { + } + + private int c() { ++ // Paper start ++ /* + while (this.e < this.d.length && this.d[this.e] != null) { + ++this.e; + } ++ */ ++ this.e = this.usedIds.nextClearBit(0); ++ // Paper end + + return this.e; + } +@@ -68,6 +75,7 @@ public class RegistryID implements Registry { + this.d = (K[]) (new Object[i]); // Paper - decompile fix + this.e = 0; + this.f = 0; ++ this.usedIds.clear(); // Paper + + for (int j = 0; j < ak.length; ++j) { + if (ak[j] != null) { +@@ -93,6 +101,7 @@ public class RegistryID implements Registry { + this.b[k] = k0; + this.c[k] = i; + this.d[i] = k0; ++ this.usedIds.set(i); // Paper + ++this.f; + if (i == this.e) { + ++this.e; +@@ -157,6 +166,7 @@ public class RegistryID implements Registry { + Arrays.fill(this.d, (Object) null); + this.e = 0; + this.f = 0; ++ this.usedIds.clear(); // Paper + } + + public int b() { diff --git a/patches/server-unmapped/0001/0248-Option-to-prevent-armor-stands-from-doing-entity-loo.patch b/patches/server-unmapped/0001/0248-Option-to-prevent-armor-stands-from-doing-entity-loo.patch new file mode 100644 index 0000000000..2c15a78257 --- /dev/null +++ b/patches/server-unmapped/0001/0248-Option-to-prevent-armor-stands-from-doing-entity-loo.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Manrique +Date: Mon, 23 Jul 2018 12:57:39 +0200 +Subject: [PATCH] Option to prevent armor stands from doing entity lookups + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 4813f62d1e382d5ac6971b2244df3f13c80d1950..3562950df4868b1393790b1a1ff1fe0dc589c155 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -379,4 +379,9 @@ public class PaperWorldConfig { + private void scanForLegacyEnderDragon() { + scanForLegacyEnderDragon = getBoolean("game-mechanics.scan-for-legacy-ender-dragon", true); + } ++ ++ public boolean armorStandEntityLookups = true; ++ private void armorStandEntityLookups() { ++ armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", true); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +index 57e0ea95df34fab22d6c5868ab839d56a3fa85fc..829013f57128cc6c92a45098c6883f2305cf4ea5 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +@@ -356,6 +356,7 @@ public class EntityArmorStand extends EntityLiving { + + @Override + protected void collideNearby() { ++ if (!world.paperConfig.armorStandEntityLookups) return; // Paper + List list = this.world.getEntities(this, this.getBoundingBox(), EntityArmorStand.br); + + for (int i = 0; i < list.size(); ++i) { +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 384855edcc9c31a554c5f603ca53b373b3c9095a..1d7cfadd7afeb5e1199d23b4a9489c05e219c18d 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -37,6 +37,7 @@ import net.minecraft.world.DifficultyDamageScaler; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.decoration.EntityArmorStand; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.crafting.CraftingManager; +@@ -854,6 +855,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + // Paper end + } + } ++ // Paper start - Prevent armor stands from doing entity lookups ++ @Override ++ public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisAlignedBB) { ++ if (entity instanceof EntityArmorStand && !entity.world.paperConfig.armorStandEntityLookups) return false; ++ return GeneratorAccess.super.getCubes(entity, axisAlignedBB); ++ } ++ // Paper end + + public Explosion explode(@Nullable Entity entity, double d0, double d1, double d2, float f, Explosion.Effect explosion_effect) { + return this.createExplosion(entity, (DamageSource) null, (ExplosionDamageCalculator) null, d0, d1, d2, f, false, explosion_effect); diff --git a/patches/server-unmapped/0001/0249-Vanished-players-don-t-have-rights.patch b/patches/server-unmapped/0001/0249-Vanished-players-don-t-have-rights.patch new file mode 100644 index 0000000000..f565797edc --- /dev/null +++ b/patches/server-unmapped/0001/0249-Vanished-players-don-t-have-rights.patch @@ -0,0 +1,194 @@ +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/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c88eea18e2e219f242c53ffb4e28cfc6d7bf318a..3a46a5001cda7402a97ac8552650cf64e7881fad 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -184,7 +184,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + private static double e = 1.0D; + private final EntityTypes f; + private int id; +- public boolean i; ++ public boolean i; public final boolean blocksEntitySpawning() { return this.i; } // Paper - OBFHELPER + public final List passengers; + protected int j; + @Nullable +diff --git a/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java +index 65cea9282467cb362ac6e9e0bb03c5d36085ee43..65cee640040bdd1229149409ff046b765ee08c34 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java +@@ -4,6 +4,7 @@ import java.util.Iterator; + import java.util.UUID; + import javax.annotation.Nullable; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.WorldServer; + import net.minecraft.util.MathHelper; + import net.minecraft.world.entity.Entity; +@@ -140,8 +141,14 @@ public abstract class IProjectile extends Entity { + protected boolean a(Entity entity) { + if (!entity.isSpectator() && entity.isAlive() && entity.isInteractable()) { + Entity entity1 = this.getShooter(); +- ++ // Paper start - Cancel hit for vanished players ++ if (entity1 instanceof EntityPlayer && entity instanceof EntityPlayer) { ++ 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; ++ } + return entity1 == null || this.d || !entity1.isSameVehicle(entity); ++ // Paper end + } else { + return false; + } +diff --git a/src/main/java/net/minecraft/world/item/ItemBlock.java b/src/main/java/net/minecraft/world/item/ItemBlock.java +index ec12bea9de910824927ba31628b49f6713f31a29..59d52c252b2e59923b8e513dd4d2e1ec9ce34dc7 100644 +--- a/src/main/java/net/minecraft/world/item/ItemBlock.java ++++ b/src/main/java/net/minecraft/world/item/ItemBlock.java +@@ -177,7 +177,8 @@ public class ItemBlock extends Item { + EntityHuman entityhuman = blockactioncontext.getEntity(); + VoxelShapeCollision voxelshapecollision = entityhuman == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a((Entity) entityhuman); + // CraftBukkit start - store default return +- boolean defaultReturn = (!this.isCheckCollisions() || iblockdata.canPlace(blockactioncontext.getWorld(), blockactioncontext.getClickPosition())) && blockactioncontext.getWorld().a(iblockdata, blockactioncontext.getClickPosition(), voxelshapecollision); ++ World world = blockactioncontext.getWorld(); // Paper ++ boolean defaultReturn = (!this.isCheckCollisions() || iblockdata.canPlace(blockactioncontext.getWorld(), blockactioncontext.getClickPosition())) && world.checkEntityCollision(iblockdata, entityhuman, voxelshapecollision, blockactioncontext.getClickPosition(), true); // Paper + org.bukkit.entity.Player player = (blockactioncontext.getEntity() instanceof EntityPlayer) ? (org.bukkit.entity.Player) blockactioncontext.getEntity().getBukkitEntity() : null; + + BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(blockactioncontext.getWorld(), blockactioncontext.getClickPosition()), player, CraftBlockData.fromData(iblockdata), defaultReturn); +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 1d7cfadd7afeb5e1199d23b4a9489c05e219c18d..aa344e30dc7e31c49bbb842bb1bb69ecc9ba688c 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -27,6 +27,7 @@ import net.minecraft.network.protocol.Packet; + import net.minecraft.resources.MinecraftKey; + import net.minecraft.resources.ResourceKey; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.PlayerChunk; + import net.minecraft.sounds.SoundCategory; + import net.minecraft.sounds.SoundEffect; +@@ -65,6 +66,10 @@ import net.minecraft.world.level.saveddata.maps.WorldMap; + import net.minecraft.world.level.storage.WorldData; + import net.minecraft.world.level.storage.WorldDataMutable; + import net.minecraft.world.phys.AxisAlignedBB; ++import net.minecraft.world.phys.shapes.OperatorBoolean; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.phys.shapes.VoxelShapeCollision; ++import net.minecraft.world.phys.shapes.VoxelShapes; + import net.minecraft.world.scores.Scoreboard; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -232,6 +237,46 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); + } + ++ // Paper start ++ // ret true if no collision ++ public final boolean checkEntityCollision(IBlockData data, Entity source, VoxelShapeCollision voxelshapedcollision, ++ BlockPosition position, boolean checkCanSee) { ++ // Copied from IWorldReader#a(IBlockData, BlockPosition, VoxelShapeCollision) & EntityAccess#a(Entity, VoxelShape) ++ VoxelShape voxelshape = data.getCollisionShape(this, position, voxelshapedcollision); ++ if (voxelshape.isEmpty()) { ++ return true; ++ } ++ ++ voxelshape = voxelshape.offset((double) position.getX(), (double) position.getY(), (double) position.getZ()); ++ if (voxelshape.isEmpty()) { ++ return true; ++ } ++ ++ List entities = this.getEntities(null, voxelshape.getBoundingBox()); ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ Entity entity = entities.get(i); ++ ++ if (checkCanSee && source instanceof EntityPlayer && entity instanceof EntityPlayer ++ && !((EntityPlayer) source).getBukkitEntity().canSee(((EntityPlayer) 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.dead || !entity.blocksEntitySpawning()) { ++ continue; ++ } ++ ++ if (VoxelShapes.applyOperation(voxelshape, VoxelShapes.of(entity.getBoundingBox()), OperatorBoolean.AND)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ // Paper end ++ + @Override + public boolean s_() { + return this.isClientSide; +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +index 2902117fd2803741b053a04fda7f4414fb8593cb..10f4015b8c36e4e27cf7d0745ba70b8a9e60aff3 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +@@ -505,6 +505,7 @@ public abstract class BlockBase { + return this.a != null ? this.a.b : this.b(iblockaccess, blockposition, VoxelShapeCollision.a()); + } + ++ public final VoxelShape getCollisionShape(IBlockAccess iblockaccess, BlockPosition blockposition, VoxelShapeCollision voxelshapecollision) { return this.b(iblockaccess, blockposition, voxelshapecollision); } // Paper - OBFHELPER + public VoxelShape b(IBlockAccess iblockaccess, BlockPosition blockposition, VoxelShapeCollision voxelshapecollision) { + return this.getBlock().c(this.p(), iblockaccess, blockposition, voxelshapecollision); + } +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 1b82349b96b3ec9490d06d1c1d1cbf2b1578d313..887016224c16f8a38c10a98eb0e2ae6cb353a153 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +@@ -54,6 +54,7 @@ public abstract class VoxelShape { + return this.a.a(); + } + ++ public final VoxelShape offset(double x, double y, double z) { return this.a(x, y, z); } // Paper - OBFHELPER + public VoxelShape a(double d0, double d1, double d2) { + return (VoxelShape) (this.isEmpty() ? VoxelShapes.a() : new VoxelShapeArray(this.a, new DoubleListOffset(this.a(EnumDirection.EnumAxis.X), d0), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Y), d1), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Z), d2))); + } +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +index fdd9e37a8c90fc3311e515355af0a0593efbdacc..cf32a4f63e8e59535c02a3f9c57f98833a2b0e83 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +@@ -45,6 +45,7 @@ public final class VoxelShapes { + return a(new AxisAlignedBB(d0, d1, d2, d3, d4, d5)); + } + ++ public static final VoxelShape of(AxisAlignedBB axisAlignedbb) { return VoxelShapes.a(axisAlignedbb); } // Paper - OBFHELPER + public static VoxelShape a(AxisAlignedBB axisalignedbb) { + int i = a(axisalignedbb.minX, axisalignedbb.maxX); + int j = a(axisalignedbb.minY, axisalignedbb.maxY); +@@ -139,6 +140,7 @@ public final class VoxelShapes { + } + } + ++ public static final boolean applyOperation(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { return VoxelShapes.c(voxelshape, voxelshape1, operatorboolean); } // Paper - OBFHELPER + public static boolean c(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { + if (operatorboolean.apply(false, false)) { + throw (IllegalArgumentException) SystemUtils.c((Throwable) (new IllegalArgumentException())); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index db8dc8e5dda304a3ccd81dc0c781476d5946a1ba..334ac1cb253cf3d063c7a26db698c596b425b057 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1202,6 +1202,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-unmapped/0001/0250-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch b/patches/server-unmapped/0001/0250-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch new file mode 100644 index 0000000000..a5f5fb4d8d --- /dev/null +++ b/patches/server-unmapped/0001/0250-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 23 Jul 2018 22:18:31 -0400 +Subject: [PATCH] Mark chunk dirty anytime entities change to guarantee it + saves + + +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index 7918dd4ad3e8cbb905b3929062a70fb7961b7d68..f56ff8e727c74870229d4d146b13534863f620d6 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -560,6 +560,7 @@ public class Chunk implements IChunkAccess { + entity.chunkZ = this.loc.z; + this.entities.add(entity); // Paper - per chunk entity list + this.entitySlices[k].add(entity); ++ this.markDirty(); // Paper + } + + @Override +@@ -588,6 +589,7 @@ public class Chunk implements IChunkAccess { + return; + } + entityCounts.decrement(entity.getMinecraftKeyString()); ++ this.markDirty(); // Paper + // Paper end + this.entities.remove(entity); // Paper + } diff --git a/patches/server-unmapped/0001/0251-Add-some-Debug-to-Chunk-Entity-slices.patch b/patches/server-unmapped/0001/0251-Add-some-Debug-to-Chunk-Entity-slices.patch new file mode 100644 index 0000000000..71c72d329e --- /dev/null +++ b/patches/server-unmapped/0001/0251-Add-some-Debug-to-Chunk-Entity-slices.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 23 Jul 2018 22:44:23 -0400 +Subject: [PATCH] Add some Debug to Chunk Entity slices + +If we detect unexpected state, log and try to recover + +This should hopefully avoid duplicate entities ever being created +if the entity was to end up in 2 different chunk slices + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 3a46a5001cda7402a97ac8552650cf64e7881fad..102c2bb98a99cdbfcdf1297341dbba91434ee0e3 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -157,6 +157,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + } + }; ++ public List entitySlice = null; + // Paper end + + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index f56ff8e727c74870229d4d146b13534863f620d6..e4accac8f2e8daa58f9b0c279ffcad9347448bb0 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -26,6 +26,8 @@ import net.minecraft.ReportedException; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.IRegistry; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkProviderServer; + import net.minecraft.server.level.PlayerChunk; + import net.minecraft.server.level.WorldServer; + import net.minecraft.util.EntitySlice; +@@ -551,6 +553,25 @@ public class Chunk implements IChunkAccess { + if (k >= this.entitySlices.length) { + k = this.entitySlices.length - 1; + } ++ // Paper - remove from any old list if its in one ++ List nextSlice = this.entitySlices[k]; // the next list to be added to ++ List currentSlice = entity.entitySlice; ++ if (nextSlice == currentSlice) { ++ if (World.DEBUG_ENTITIES) MinecraftServer.LOGGER.warn("Entity was already in this chunk!" + entity, new Throwable()); ++ return; // ??? silly plugins ++ } ++ if (currentSlice != null && currentSlice.contains(entity)) { ++ // Still in an old chunk... ++ if (World.DEBUG_ENTITIES) MinecraftServer.LOGGER.warn("Entity is still in another chunk!" + entity, new Throwable()); ++ Chunk chunk = entity.getCurrentChunk(); ++ if (chunk != null) { ++ chunk.removeEntity(entity); ++ } else { ++ removeEntity(entity); ++ } ++ currentSlice.remove(entity); // Just incase the above did not remove from the previous slice ++ } ++ // Paper end + + if (!entity.inChunk || entity.getCurrentChunk() != this) entityCounts.increment(entity.getMinecraftKeyString()); // Paper + entity.inChunk = true; +@@ -560,6 +581,7 @@ public class Chunk implements IChunkAccess { + entity.chunkZ = this.loc.z; + this.entities.add(entity); // Paper - per chunk entity list + this.entitySlices[k].add(entity); ++ entity.entitySlice = this.entitySlices[k]; // Paper + this.markDirty(); // Paper + } + +@@ -585,6 +607,10 @@ public class Chunk implements IChunkAccess { + + // Paper start + if (entity.currentChunk != null && entity.currentChunk.get() == this) entity.setCurrentChunk(null); ++ if (entitySlices[i] == entity.entitySlice) { ++ entity.entitySlice = null; ++ entity.inChunk = false; ++ } + if (!this.entitySlices[i].remove(entity)) { + return; + } diff --git a/patches/server-unmapped/0001/0252-SkeletonHorse-Additions.patch b/patches/server-unmapped/0001/0252-SkeletonHorse-Additions.patch new file mode 100644 index 0000000000..96ea01f30a --- /dev/null +++ b/patches/server-unmapped/0001/0252-SkeletonHorse-Additions.patch @@ -0,0 +1,152 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 27 Jul 2018 22:36:31 -0500 +Subject: [PATCH] SkeletonHorse Additions + + +diff --git a/src/main/java/net/minecraft/world/entity/IEntitySelector.java b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +index dfcfdb31ca9531913d705aaaf85fb67399cfdc8c..4776a47566aac487dc77fd6b4b9b42b95974e31a 100644 +--- a/src/main/java/net/minecraft/world/entity/IEntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +@@ -25,6 +25,7 @@ public final class IEntitySelector { + public static final Predicate f = (entity) -> { + return !(entity instanceof EntityHuman) || !entity.isSpectator() && !((EntityHuman) entity).isCreative() && entity.world.getDifficulty() != EnumDifficulty.PEACEFUL; + }; ++ public static Predicate notSpectator() { return g; } // Paper - OBFHELPER + public static final Predicate g = (entity) -> { + return !entity.isSpectator(); + }; +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseSkeleton.java b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseSkeleton.java +index 0ddb676a2d1c38c97cd7750c1594fd6468156d0a..da5365372e89b847d626e52c5541544467f14702 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseSkeleton.java +@@ -26,7 +26,7 @@ public class EntityHorseSkeleton extends EntityHorseAbstract { + + private final PathfinderGoalHorseTrap bw = new PathfinderGoalHorseTrap(this); + private boolean bx; +- private int by; ++ private int by; public int getTrapTime() { return this.by; } // Paper - OBFHELPER + + public EntityHorseSkeleton(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -145,10 +145,12 @@ public class EntityHorseSkeleton extends EntityHorseAbstract { + return 0.96F; + } + ++ public boolean isTrap() { return this.eM(); } // Paper - OBFHELPER + public boolean eM() { + return this.bx; + } + ++ public void setTrap(boolean trap) { this.t(trap); } // Paper - OBFHELPER + public void t(boolean flag) { + if (flag != this.bx) { + this.bx = flag; +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/PathfinderGoalHorseTrap.java b/src/main/java/net/minecraft/world/entity/animal/horse/PathfinderGoalHorseTrap.java +index 9b6a4f93dca6eeddad43d5f5675c551fb3fd2fdb..cf250cc6b7a77a7af742eab0b89cff2bc17fc5e4 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/PathfinderGoalHorseTrap.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/PathfinderGoalHorseTrap.java +@@ -15,9 +15,14 @@ import net.minecraft.world.item.Items; + import net.minecraft.world.item.enchantment.EnchantmentManager; + import net.minecraft.world.level.World; + ++import org.bukkit.entity.HumanEntity; ++ ++import java.util.List; ++ + public class PathfinderGoalHorseTrap extends PathfinderGoal { + + private final EntityHorseSkeleton a; ++ private List eligiblePlayers; // Paper + + public PathfinderGoalHorseTrap(EntityHorseSkeleton entityhorseskeleton) { + this.a = entityhorseskeleton; +@@ -25,12 +30,13 @@ public class PathfinderGoalHorseTrap extends PathfinderGoal { + + @Override + public boolean a() { +- return this.a.world.isPlayerNearby(this.a.locX(), this.a.locY(), this.a.locZ(), 10.0D); ++ return !(eligiblePlayers = this.a.world.findNearbyBukkitPlayers(this.a.locX(), this.a.locY(), this.a.locZ(), 10.0D, false)).isEmpty(); // Paper + } + + @Override + public void e() { + WorldServer worldserver = (WorldServer) this.a.world; ++ if (!new com.destroystokyo.paper.event.entity.SkeletonHorseTrapEvent((org.bukkit.entity.SkeletonHorse) this.a.getBukkitEntity(), eligiblePlayers).callEvent()) return; // Paper + DifficultyDamageScaler difficultydamagescaler = worldserver.getDamageScaler(this.a.getChunkCoordinates()); + + this.a.t(false); +diff --git a/src/main/java/net/minecraft/world/level/IEntityAccess.java b/src/main/java/net/minecraft/world/level/IEntityAccess.java +index 6d5d4c3df65995b9a13b66d070ba08d553cc98a2..8fdc4b22e8c99d653bd213fe64339c133b46b4e9 100644 +--- a/src/main/java/net/minecraft/world/level/IEntityAccess.java ++++ b/src/main/java/net/minecraft/world/level/IEntityAccess.java +@@ -1,6 +1,9 @@ + package net.minecraft.world.level; + ++import com.google.common.collect.ImmutableList; + import com.google.common.collect.Lists; ++import org.bukkit.entity.HumanEntity; ++ + import java.util.Iterator; + import java.util.List; + import java.util.UUID; +@@ -115,6 +118,28 @@ public interface IEntityAccess { + return entityhuman; + } + ++ // Paper start ++ default List findNearbyBukkitPlayers(double x, double y, double z, double radius, boolean notSpectator) { ++ return findNearbyBukkitPlayers(x, y, z, radius, notSpectator ? IEntitySelector.notSpectator() : IEntitySelector.canAITarget()); ++ } ++ ++ default List findNearbyBukkitPlayers(double x, double y, double z, double radius, @Nullable Predicate predicate) { ++ ImmutableList.Builder builder = ImmutableList.builder(); ++ ++ for (EntityHuman human : this.getPlayers()) { ++ if (predicate == null || predicate.test(human)) { ++ double distanceSquared = human.getDistanceSquared(x, y, z); ++ ++ if (radius < 0.0D || distanceSquared < radius * radius) { ++ builder.add(human.getBukkitEntity()); ++ } ++ } ++ } ++ ++ return builder.build(); ++ } ++ // Paper end ++ + @Nullable + default EntityHuman findNearbyPlayer(Entity entity, double d0) { + return this.a(entity.locX(), entity.locY(), entity.locZ(), d0, false); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java +index 4aec5fdb6b0379b0797969ff3c8f8a9629a18bfb..7366d56cefad45883f353ea5fb16b41f87006ec1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java +@@ -26,4 +26,26 @@ public class CraftSkeletonHorse extends CraftAbstractHorse implements SkeletonHo + public Variant getVariant() { + return Variant.SKELETON_HORSE; + } ++ ++ // Paper start ++ @Override ++ public EntityHorseSkeleton getHandle() { ++ return (EntityHorseSkeleton) super.getHandle(); ++ } ++ ++ @Override ++ public int getTrapTime() { ++ return getHandle().getTrapTime(); ++ } ++ ++ @Override ++ public boolean isTrap() { ++ return getHandle().isTrap(); ++ } ++ ++ @Override ++ public void setTrap(boolean trap) { ++ getHandle().setTrap(trap); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0253-Prevent-Saving-Bad-entities-to-chunks.patch b/patches/server-unmapped/0001/0253-Prevent-Saving-Bad-entities-to-chunks.patch new file mode 100644 index 0000000000..d16a82a8f8 --- /dev/null +++ b/patches/server-unmapped/0001/0253-Prevent-Saving-Bad-entities-to-chunks.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 26 Jul 2018 00:11:12 -0400 +Subject: [PATCH] Prevent Saving Bad entities to chunks + +See https://github.com/PaperMC/Paper/issues/1223 + +Minecraft is saving invalid entities to the chunk files. + +Avoid saving bad data, and also make improvements to handle +loading these chunks. Any invalid entity will be instant killed, +so lets avoid adding it to the world... + +This lets us be safer about the dupe UUID resolver too, as now +we can ignore instant killed entities and avoid risk of duplicating +an invalid entity. + +This should reduce log occurrences of dupe uuid messages. + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 14321bc6ecc5ca70e71c1eef9578091822aa94cd..ce7431ea8597c645bb2c97f596796dbf12206e72 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1157,6 +1157,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + List[] aentityslice = chunk.getEntitySlices(); // Spigot + int i = aentityslice.length; + ++ java.util.List toMoveChunks = new java.util.ArrayList<>(); // Paper + for (int j = 0; j < i; ++j) { + List entityslice = aentityslice[j]; // Spigot + Iterator iterator = entityslice.iterator(); +@@ -1169,11 +1170,25 @@ public class WorldServer extends World implements GeneratorAccessSeed { + throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException("Removing entity while ticking!"))); + } + ++ // Paper start - move out entities that shouldn't be in this chunk before it unloads ++ if (!entity.dead && (int) Math.floor(entity.locX()) >> 4 != chunk.getPos().x || (int) Math.floor(entity.locZ()) >> 4 != chunk.getPos().z) { ++ toMoveChunks.add(entity); ++ continue; ++ } ++ // Paper end ++ + this.entitiesById.remove(entity.getId()); + this.unregisterEntity(entity); ++ ++ if (entity.dead) iterator.remove(); // Paper - don't save dead entities during unload + } + } + } ++ // Paper start - move out entities that shouldn't be in this chunk before it unloads ++ for (Entity entity : toMoveChunks) { ++ this.chunkCheck(entity); ++ } ++ // Paper end + + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +index f301c7ba4b17b92c6cf2fcee6da1e67081dad4fa..69bc9dc18bab157851d8080a672504598e8572a8 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +@@ -26,6 +26,7 @@ import net.minecraft.nbt.NBTTagList; + import net.minecraft.nbt.NBTTagLongArray; + import net.minecraft.nbt.NBTTagShort; + import net.minecraft.server.level.ChunkProviderServer; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.LightEngineThreaded; + import net.minecraft.server.level.WorldServer; + import net.minecraft.world.entity.Entity; +@@ -349,6 +350,7 @@ public class ChunkRegionLoader { + nbttagcompound1.set("TileEntities", nbttaglist1); + NBTTagList nbttaglist2 = new NBTTagList(); + ++ java.util.List toUpdate = new java.util.ArrayList<>(); // Paper + if (ichunkaccess.getChunkStatus().getType() == ChunkStatus.Type.LEVELCHUNK) { + Chunk chunk = (Chunk) ichunkaccess; + +@@ -366,13 +368,28 @@ public class ChunkRegionLoader { + while (iterator1.hasNext()) { + Entity entity = (Entity) iterator1.next(); + NBTTagCompound nbttagcompound4 = new NBTTagCompound(); +- ++ // Paper start ++ if ((int) Math.floor(entity.locX()) >> 4 != chunk.getPos().x || (int) Math.floor(entity.locZ()) >> 4 != chunk.getPos().z) { ++ toUpdate.add(entity); ++ continue; ++ } ++ if (entity.dead || hasPlayerPassenger(entity)) { ++ continue; ++ } ++ // Paper end + if (entity.d(nbttagcompound4)) { + chunk.d(true); + nbttaglist2.add(nbttagcompound4); + } + } + } ++ ++ // Paper start - move entities to the correct chunk ++ for (Entity entity : toUpdate) { ++ worldserver.chunkCheck(entity); ++ } ++ // Paper end ++ + } else { + ProtoChunk protochunk = (ProtoChunk) ichunkaccess; + +@@ -431,6 +448,19 @@ public class ChunkRegionLoader { + nbttagcompound1.set("Structures", a(chunkcoordintpair, ichunkaccess.h(), ichunkaccess.v())); + return nbttagcompound; + } ++ // Paper start - this is saved with the player ++ private static boolean hasPlayerPassenger(Entity entity) { ++ for (Entity passenger : entity.passengers) { ++ if (passenger instanceof EntityPlayer) { ++ return true; ++ } ++ if (hasPlayerPassenger(passenger)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ // Paper end + + public static ChunkStatus.Type a(@Nullable NBTTagCompound nbttagcompound) { + if (nbttagcompound != null) { diff --git a/patches/server-unmapped/0001/0254-Don-t-call-getItemMeta-on-hasItemMeta.patch b/patches/server-unmapped/0001/0254-Don-t-call-getItemMeta-on-hasItemMeta.patch new file mode 100644 index 0000000000..ab3543b44f --- /dev/null +++ b/patches/server-unmapped/0001/0254-Don-t-call-getItemMeta-on-hasItemMeta.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Manrique +Date: Thu, 26 Jul 2018 14:10:23 +0200 +Subject: [PATCH] Don't call getItemMeta on hasItemMeta + +Spigot 1.13 checks if any field (which are manually copied from the ItemStack's "tag" NBT tag) on the ItemMeta class of an ItemStack is set. + +We could just check if the "tag" NBT tag is empty, albeit that would break some plugins. The only general tag added on 1.13 is "Damage", and we can just check if the "tag" NBT tag contains any other tag that's not "Damage" (https://minecraft.gamepedia.com/Player.dat_format#Item_structure) making the `hasItemStack` method behave as before. + +Returns true if getDamage() == 0 or has damage tag or other tag is set. +Check the `ItemMetaTest#testTaggedButNotMeta` method to see how this method behaves. + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 315addab147dfecf4aa88d32d154cefe850d0a78..09d7a86b5f6cffdf6664ad657dd4f8dd8eabdd70 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -570,7 +570,7 @@ public final class CraftItemStack extends ItemStack { + + @Override + public boolean hasItemMeta() { +- return hasItemMeta(handle) && !CraftItemFactory.instance().equals(getItemMeta(), null); ++ return hasItemMeta(handle) && (handle.getDamage() != 0 || (handle.getTag() != null && handle.getTag().map.size() >= (handle.getTag().hasKey(CraftMetaItem.DAMAGE.NBT) ? 2 : 1))); // Paper - keep 1.12 CraftBukkit behavior without calling getItemMeta + } + + static boolean hasItemMeta(net.minecraft.world.item.ItemStack item) { +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +index 42f577ed3508ba5a380648461e149f16ce97c9bd..b85a0a4c4f134dd6012d9141244ecf97b4300b65 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +@@ -96,6 +96,34 @@ public class ItemMetaTest extends AbstractTestingBase { + assertThat(itemMeta.hasConflictingEnchant(null), is(false)); + } + ++ // Paper start ++ private void testItemMeta(ItemStack stack) { ++ assertThat("Should not have ItemMeta", stack.hasItemMeta(), is(false)); ++ ++ stack.setDurability((short) 0); ++ assertThat("ItemStack with zero durability should not have ItemMeta", stack.hasItemMeta(), is(false)); ++ ++ stack.setDurability((short) 2); ++ assertThat("ItemStack with non-zero durability should have ItemMeta", stack.hasItemMeta(), is(true)); ++ ++ stack.setLore(java.util.Collections.singletonList("Lore")); ++ assertThat("ItemStack with lore and durability should have ItemMeta", stack.hasItemMeta(), is(true)); ++ ++ stack.setDurability((short) 0); ++ assertThat("ItemStack with lore should have ItemMeta", stack.hasItemMeta(), is(true)); ++ ++ stack.setLore(null); ++ } ++ ++ @Test ++ public void testHasItemMeta() { ++ ItemStack itemStack = new ItemStack(Material.SHEARS); ++ ++ testItemMeta(itemStack); ++ testItemMeta(CraftItemStack.asCraftCopy(itemStack)); ++ } ++ // Paper end ++ + @Test + public void testConflictingStoredEnchantment() { + EnchantmentStorageMeta itemMeta = (EnchantmentStorageMeta) Bukkit.getItemFactory().getItemMeta(Material.ENCHANTED_BOOK); diff --git a/patches/server-unmapped/0001/0255-Ignore-Dead-Entities-in-entityList-iteration.patch b/patches/server-unmapped/0001/0255-Ignore-Dead-Entities-in-entityList-iteration.patch new file mode 100644 index 0000000000..1a77ecf605 --- /dev/null +++ b/patches/server-unmapped/0001/0255-Ignore-Dead-Entities-in-entityList-iteration.patch @@ -0,0 +1,120 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 28 Jul 2018 12:18:27 -0400 +Subject: [PATCH] Ignore Dead Entities in entityList iteration + +A spigot change delays removal of entities from the entity list. +This causes a change in behavior from Vanilla where getEntities type +methods will return dead entities that they shouldn't otherwise be doing. + +This will ensure that dead entities are skipped from iteration since +they shouldn't of been in the list in the first place. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index 6943524c2dd8b12691b8ac5b08daee823ce50c3d..b67bd98cca4a06bc0ebaed577195dffc3b3251ec 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -209,6 +209,7 @@ public class PaperCommand extends Command { + Collection entities = world.entitiesById.values(); + entities.forEach(e -> { + MinecraftKey key = e.getMinecraftKey(); ++ if (e.shouldBeRemoved) return; // Paper + + MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); + ChunkCoordIntPair chunk = new ChunkCoordIntPair(e.chunkX, e.chunkZ); +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index ce7431ea8597c645bb2c97f596796dbf12206e72..4eda8d2065d72c67b1e1cf9e9560e13e7f24d470 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1309,6 +1309,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + entity.origin = entity.getBukkitEntity().getLocation(); + } + // Paper end ++ entity.shouldBeRemoved = false; // Paper - shouldn't be removed after being re-added + new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid + } + +@@ -1321,6 +1322,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.removeEntityFromChunk(entity); + this.entitiesById.remove(entity.getId()); + this.unregisterEntity(entity); ++ entity.shouldBeRemoved = true; // Paper + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 102c2bb98a99cdbfcdf1297341dbba91434ee0e3..046b191e771ed9be337e095214a67febd768e5f6 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -276,6 +276,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + protected int numCollisions = 0; // Paper + public void inactiveTick() { } + // Spigot end ++ public boolean shouldBeRemoved; // Paper + + public float getBukkitYaw() { + return this.yaw; +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index e4accac8f2e8daa58f9b0c279ffcad9347448bb0..79ff96f18c53f3d1ce4a00be2e2d8fe68f77bf54 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -863,6 +863,7 @@ public class Chunk implements IChunkAccess { + + for (int i1 = 0; i1 < l; ++i1) { + Entity entity1 = (Entity) list1.get(i1); ++ if (entity1.shouldBeRemoved) continue; // Paper + + if (entity1.getBoundingBox().c(axisalignedbb) && entity1 != entity) { + if (predicate == null || predicate.test(entity1)) { +@@ -900,6 +901,7 @@ public class Chunk implements IChunkAccess { + + while (iterator.hasNext()) { + T entity = (T) iterator.next(); // CraftBukkit - decompile error ++ if (entity.shouldBeRemoved) continue; // Paper + + if ((entitytypes == null || entity.getEntityType() == entitytypes) && entity.getBoundingBox().c(axisalignedbb) && predicate.test(entity)) { + list.add(entity); +@@ -922,6 +924,7 @@ public class Chunk implements IChunkAccess { + + while (iterator.hasNext()) { + T t0 = (T) iterator.next(); // CraftBukkit - decompile error ++ if (t0.shouldBeRemoved) continue; // Paper + + if (oclass.isInstance(t0) && t0.getBoundingBox().c(axisalignedbb) && (predicate == null || predicate.test(t0))) { // Spigot - instance check + list.add(t0); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 945ea6022d89bfb6ee4429233909f8294141fbb6..716974f9f455a8b35a2bee0ef17e795676f44105 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1040,6 +1040,7 @@ public class CraftWorld implements World { + for (Object o : world.entitiesById.values()) { + if (o instanceof net.minecraft.world.entity.Entity) { + net.minecraft.world.entity.Entity mcEnt = (net.minecraft.world.entity.Entity) o; ++ if (mcEnt.shouldBeRemoved) continue; // Paper + Entity bukkitEntity = mcEnt.getBukkitEntity(); + + // Assuming that bukkitEntity isn't null +@@ -1059,6 +1060,7 @@ public class CraftWorld implements World { + for (Object o : world.entitiesById.values()) { + if (o instanceof net.minecraft.world.entity.Entity) { + net.minecraft.world.entity.Entity mcEnt = (net.minecraft.world.entity.Entity) o; ++ if (mcEnt.shouldBeRemoved) continue; // Paper + Entity bukkitEntity = mcEnt.getBukkitEntity(); + + // Assuming that bukkitEntity isn't null +@@ -1085,6 +1087,7 @@ public class CraftWorld implements World { + + for (Object entity: world.entitiesById.values()) { + if (entity instanceof net.minecraft.world.entity.Entity) { ++ if (((net.minecraft.world.entity.Entity) entity).shouldBeRemoved) continue; // Paper + Entity bukkitEntity = ((net.minecraft.world.entity.Entity) entity).getBukkitEntity(); + + if (bukkitEntity == null) { +@@ -1108,6 +1111,7 @@ public class CraftWorld implements World { + + for (Object entity: world.entitiesById.values()) { + if (entity instanceof net.minecraft.world.entity.Entity) { ++ if (((net.minecraft.world.entity.Entity) entity).shouldBeRemoved) continue; // Paper + Entity bukkitEntity = ((net.minecraft.world.entity.Entity) entity).getBukkitEntity(); + + if (bukkitEntity == null) { diff --git a/patches/server-unmapped/0001/0256-Implement-Expanded-ArmorStand-API.patch b/patches/server-unmapped/0001/0256-Implement-Expanded-ArmorStand-API.patch new file mode 100644 index 0000000000..55ff4fad22 --- /dev/null +++ b/patches/server-unmapped/0001/0256-Implement-Expanded-ArmorStand-API.patch @@ -0,0 +1,112 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: willies952002 +Date: Thu, 26 Jul 2018 02:25:46 -0400 +Subject: [PATCH] Implement Expanded ArmorStand API + +Add the following: +- Add proper methods for getting and setting items in both hands. Deprecates old methods +- Enable/Disable slot interactions + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +index 829013f57128cc6c92a45098c6883f2305cf4ea5..e97d25339b37a70f91022dcb021bbe82fb8f5eda 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +@@ -430,6 +430,7 @@ public class EntityArmorStand extends EntityLiving { + return enumitemslot; + } + ++ public final boolean isSlotDisabled(EnumItemSlot slot) { return this.d(slot); } // Paper - OBFHELPER + private boolean d(EnumItemSlot enumitemslot) { + return (this.disabledSlots & 1 << enumitemslot.getSlotFlag()) != 0 || enumitemslot.a() == EnumItemSlot.Function.HAND && !this.hasArms(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +index 6f922e4cbb095439fcd76ee0d0c08bc4160b8107..103f935d9b7a2cbe9639528c587d8ac2e5f14d07 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +@@ -1,6 +1,7 @@ + package org.bukkit.craftbukkit.entity; + + import net.minecraft.core.Vector3f; ++import net.minecraft.world.entity.EnumItemSlot; + import net.minecraft.world.entity.decoration.EntityArmorStand; + import org.bukkit.craftbukkit.CraftEquipmentSlot; + import org.bukkit.craftbukkit.CraftServer; +@@ -239,5 +240,78 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + public void setCanMove(boolean move) { + getHandle().canMove = move; + } ++ ++ @Override ++ public ItemStack getItem(org.bukkit.inventory.EquipmentSlot slot) { ++ com.google.common.base.Preconditions.checkNotNull(slot, "slot"); ++ return getHandle().getEquipment(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)).asBukkitMirror(); ++ } ++ ++ @Override ++ public void setItem(org.bukkit.inventory.EquipmentSlot slot, ItemStack item) { ++ com.google.common.base.Preconditions.checkNotNull(slot, "slot"); ++ switch (slot) { ++ case HAND: ++ getEquipment().setItemInMainHand(item); ++ return; ++ case OFF_HAND: ++ getEquipment().setItemInOffHand(item); ++ return; ++ case FEET: ++ setBoots(item); ++ return; ++ case LEGS: ++ setLeggings(item); ++ return; ++ case CHEST: ++ setChestplate(item); ++ return; ++ case HEAD: ++ setHelmet(item); ++ return; ++ } ++ throw new UnsupportedOperationException(slot.name()); ++ } ++ ++ @Override ++ public java.util.Set getDisabledSlots() { ++ java.util.Set disabled = new java.util.HashSet<>(); ++ for (org.bukkit.inventory.EquipmentSlot slot : org.bukkit.inventory.EquipmentSlot.values()) { ++ if (this.isSlotDisabled(slot)) { ++ disabled.add(slot); ++ } ++ } ++ return disabled; ++ } ++ ++ @Override ++ public void setDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { ++ int disabled = 0; ++ for (org.bukkit.inventory.EquipmentSlot slot : slots) { ++ if (slot == org.bukkit.inventory.EquipmentSlot.OFF_HAND) continue; ++ EnumItemSlot nmsSlot = org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot); ++ disabled += (1 << nmsSlot.getSlotFlag()) + (1 << (nmsSlot.getSlotFlag() + 8)) + (1 << (nmsSlot.getSlotFlag() + 16)); ++ } ++ getHandle().disabledSlots = disabled; ++ } ++ ++ @Override ++ public void addDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { ++ java.util.Set disabled = getDisabledSlots(); ++ java.util.Collections.addAll(disabled, slots); ++ setDisabledSlots(disabled.toArray(new org.bukkit.inventory.EquipmentSlot[0])); ++ } ++ ++ @Override ++ public void removeDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { ++ java.util.Set disabled = getDisabledSlots(); ++ for (final org.bukkit.inventory.EquipmentSlot slot : slots) disabled.remove(slot); ++ setDisabledSlots(disabled.toArray(new org.bukkit.inventory.EquipmentSlot[0])); ++ } ++ ++ @Override ++ public boolean isSlotDisabled(org.bukkit.inventory.EquipmentSlot slot) { ++ return getHandle().isSlotDisabled(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0257-AnvilDamageEvent.patch b/patches/server-unmapped/0001/0257-AnvilDamageEvent.patch new file mode 100644 index 0000000000..3862e6017e --- /dev/null +++ b/patches/server-unmapped/0001/0257-AnvilDamageEvent.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 20 Jul 2018 23:37:03 -0500 +Subject: [PATCH] AnvilDamageEvent + + +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java b/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java +index 9658c8206b5bab07838284935e9d5e0879e405f2..ff618bbb3fc4acfce51f5e5e6a504a63e9ad77cd 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java +@@ -80,7 +80,16 @@ public class ContainerAnvil extends ContainerAnvilAbstract { + + if (!entityhuman.abilities.canInstantlyBuild && iblockdata.a((Tag) TagsBlock.ANVIL) && entityhuman.getRandom().nextFloat() < 0.12F) { + IBlockData iblockdata1 = BlockAnvil.c(iblockdata); +- ++ // Paper start ++ com.destroystokyo.paper.event.block.AnvilDamagedEvent event = new com.destroystokyo.paper.event.block.AnvilDamagedEvent(getBukkitView(), iblockdata1 != null ? org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(iblockdata1) : null); ++ if (!event.callEvent()) { ++ return; ++ } else if (event.getDamageState() == com.destroystokyo.paper.event.block.AnvilDamagedEvent.DamageState.BROKEN) { ++ iblockdata1 = null; ++ } else { ++ iblockdata1 = ((org.bukkit.craftbukkit.block.data.CraftBlockData) event.getDamageState().getMaterial().createBlockData()).getState().set(BlockAnvil.FACING, iblockdata.get(BlockAnvil.FACING)); ++ } ++ // Paper end + if (iblockdata1 == null) { + world.a(blockposition, false); + world.triggerEffect(1029, blockposition, 0); diff --git a/patches/server-unmapped/0001/0258-Add-TNTPrimeEvent.patch b/patches/server-unmapped/0001/0258-Add-TNTPrimeEvent.patch new file mode 100644 index 0000000000..e848d18de0 --- /dev/null +++ b/patches/server-unmapped/0001/0258-Add-TNTPrimeEvent.patch @@ -0,0 +1,148 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Mon, 16 Jul 2018 00:05:05 +0300 +Subject: [PATCH] Add TNTPrimeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java +index 97ef4c65c8cc569a99d9697f56bd44d32b151328..51993191e01f55e16667c25b8b57d6a6ddaf493b 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java +@@ -61,6 +61,7 @@ import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.event.entity.EntityExplodeEvent; + import org.bukkit.event.entity.EntityRegainHealthEvent; + // CraftBukkit end ++import com.destroystokyo.paper.event.block.TNTPrimeEvent; // Paper - TNTPrimeEvent + + public class EntityEnderDragon extends EntityInsentient implements IMonster { + +@@ -515,6 +516,11 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + }); + craftBlock.getNMS().dropNaturally((WorldServer) world, blockposition, ItemStack.b); + } ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ if(!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getSource().getBukkitEntity()).callEvent()) ++ continue; ++ // Paper end + nmsBlock.wasExploded(world, blockposition, explosionSource); + + this.world.a(blockposition, false); +diff --git a/src/main/java/net/minecraft/world/level/block/BlockFire.java b/src/main/java/net/minecraft/world/level/block/BlockFire.java +index c22fad0038fdb0769e23db782e3341206fbd80f9..5ef38414d87fbce453e3ab11579c89a8ff089ae0 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockFire.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockFire.java +@@ -3,6 +3,7 @@ package net.minecraft.world.level.block; + import com.google.common.collect.ImmutableMap; + import it.unimi.dsi.fastutil.objects.Object2IntMap; + import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import com.destroystokyo.paper.event.block.TNTPrimeEvent; // Paper - TNTPrimeEvent + import java.util.Map; + import java.util.Random; + import java.util.function.Function; +@@ -11,6 +12,7 @@ import net.minecraft.SystemUtils; + import net.minecraft.core.BaseBlockPosition; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.WorldServer; + import net.minecraft.world.item.context.BlockActionContext; + import net.minecraft.world.level.GameRules; +@@ -289,7 +291,7 @@ public class BlockFire extends BlockFireAbstract { + + world.setTypeAndData(blockposition, this.a(world, blockposition, l), 3); + } else { +- world.a(blockposition, false); ++ if(iblockdata.getBlock() != Blocks.TNT) world.a(blockposition, false); // Paper - TNTPrimeEvent - We might be cancelling it below, move the setAir down + } + + Block block = iblockdata.getBlock(); +@@ -297,6 +299,13 @@ public class BlockFire extends BlockFireAbstract { + if (block instanceof BlockTNT) { + BlockTNT blocktnt = (BlockTNT) block; + ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = MCUtil.toBukkitBlock(world, blockposition); ++ if (!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.FIRE, null).callEvent()) { ++ return; ++ } ++ world.setAir(blockposition, false); ++ // Paper end + BlockTNT.a(world, blockposition); + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/BlockTNT.java b/src/main/java/net/minecraft/world/level/block/BlockTNT.java +index b5d40898bb4a10b3170cd1f42f1a44de539d53c3..71c46d5042f7e4585b364682cd5464edccdf43f6 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockTNT.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockTNT.java +@@ -22,6 +22,7 @@ import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.level.block.state.properties.BlockProperties; + import net.minecraft.world.level.block.state.properties.BlockStateBoolean; + import net.minecraft.world.phys.MovingObjectPositionBlock; ++import com.destroystokyo.paper.event.block.TNTPrimeEvent; // Paper - TNTPrimeEvent + + public class BlockTNT extends Block { + +@@ -36,6 +37,11 @@ public class BlockTNT extends Block { + public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { + if (!iblockdata1.a(iblockdata.getBlock())) { + if (world.isBlockIndirectlyPowered(blockposition)) { ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = net.minecraft.server.MCUtil.toBukkitBlock(world, blockposition);; ++ if(!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) ++ return; ++ // Paper end + a(world, blockposition); + world.a(blockposition, false); + } +@@ -46,6 +52,11 @@ public class BlockTNT extends Block { + @Override + public void doPhysics(IBlockData iblockdata, World world, BlockPosition blockposition, Block block, BlockPosition blockposition1, boolean flag) { + if (world.isBlockIndirectlyPowered(blockposition)) { ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = net.minecraft.server.MCUtil.toBukkitBlock(world, blockposition);; ++ if(!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) ++ return; ++ // Paper end + a(world, blockposition); + world.a(blockposition, false); + } +@@ -64,6 +75,12 @@ public class BlockTNT extends Block { + @Override + public void wasExploded(World world, BlockPosition blockposition, Explosion explosion) { + if (!world.isClientSide) { ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = net.minecraft.server.MCUtil.toBukkitBlock(world, blockposition); ++ org.bukkit.entity.Entity source = explosion.source != null ? explosion.source.getBukkitEntity() : null; ++ if(!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.EXPLOSION, source).callEvent()) ++ return; ++ // Paper end + EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, explosion.getSource()); + + entitytntprimed.setFuseTicks((short) (world.random.nextInt(entitytntprimed.getFuseTicks() / 4) + entitytntprimed.getFuseTicks() / 8)); +@@ -92,6 +109,11 @@ public class BlockTNT extends Block { + if (item != Items.FLINT_AND_STEEL && item != Items.FIRE_CHARGE) { + return super.interact(iblockdata, world, blockposition, entityhuman, enumhand, movingobjectpositionblock); + } else { ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = net.minecraft.server.MCUtil.toBukkitBlock(world, blockposition); ++ if(!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.ITEM, entityhuman.getBukkitEntity()).callEvent()) ++ return EnumInteractionResult.FAIL; ++ // Paper end + a(world, blockposition, (EntityLiving) entityhuman); + world.setTypeAndData(blockposition, Blocks.AIR.getBlockData(), 11); + if (!entityhuman.isCreative()) { +@@ -121,6 +143,13 @@ public class BlockTNT extends Block { + } + // CraftBukkit end + ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = net.minecraft.server.MCUtil.toBukkitBlock(world, blockposition); ++ if (!new TNTPrimeEvent(tntBlock, TNTPrimeEvent.PrimeReason.PROJECTILE, iprojectile.getBukkitEntity()).callEvent()) { ++ return; ++ } ++ // Paper end ++ + a(world, blockposition, entity instanceof EntityLiving ? (EntityLiving) entity : null); + world.a(blockposition, false); + } diff --git a/patches/server-unmapped/0001/0259-Break-up-and-make-tab-spam-limits-configurable.patch b/patches/server-unmapped/0001/0259-Break-up-and-make-tab-spam-limits-configurable.patch new file mode 100644 index 0000000000..2ed97a27fd --- /dev/null +++ b/patches/server-unmapped/0001/0259-Break-up-and-make-tab-spam-limits-configurable.patch @@ -0,0 +1,75 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 77a03abd59db4a43f6f2d59d4c7ef176e782f205..bd508025b771424c942fd856c31d520b6f548082 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -296,4 +296,18 @@ public class PaperConfig { + Bukkit.getLogger().log(Level.INFO, "Using Aikar's Alternative Luck Formula to apply Luck attribute to all loot pool calculations. See https://luckformula.emc.gs"); + } + } ++ ++ public static int tabSpamIncrement = 1; ++ public static int tabSpamLimit = 500; ++ private static void tabSpamLimiters() { ++ tabSpamIncrement = getInt("settings.spam-limiter.tab-spam-increment", tabSpamIncrement); ++ // Older versions used a smaller limit, which is too low for 1.13, we'll bump this up if default ++ if (version < 14) { ++ if (tabSpamIncrement == 10) { ++ set("settings.spam-limiter.tab-spam-increment", 2); ++ tabSpamIncrement = 2; ++ } ++ } ++ tabSpamLimit = getInt("settings.spam-limiter.tab-spam-limit", tabSpamLimit); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 19763a27ad54866b7d1f6e2cfccd6bbe6e54637e..74942bf7641eafab85202fd6a521780ce4cc1f65 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -230,6 +230,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + // CraftBukkit start - multithreaded fields + private volatile int chatThrottle; + private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(PlayerConnection.class, "chatThrottle"); ++ private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits + // CraftBukkit end + private int j; + private final Int2ShortMap k = new Int2ShortOpenHashMap(); +@@ -365,6 +366,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.minecraftServer.getMethodProfiler().exit(); + // CraftBukkit start + for (int spam; (spam = this.chatThrottle) > 0 && !chatSpamField.compareAndSet(this, spam, spam - 1); ) ; ++ if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - split to seperate variable + /* Use thread-safe field access instead + if (this.chatThrottle > 0) { + --this.chatThrottle; +@@ -716,7 +718,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + public void a(PacketPlayInTabComplete packetplayintabcomplete) { + // PlayerConnectionUtils.ensureMainThread(packetplayintabcomplete, this, this.player.getWorldServer()); // Paper - run this async + // CraftBukkit start +- if (chatSpamField.addAndGet(this, 1) > 500 && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) { ++ if (tabSpamLimiter.addAndGet(com.destroystokyo.paper.PaperConfig.tabSpamIncrement) > com.destroystokyo.paper.PaperConfig.tabSpamLimit && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) { // Paper start - split and make configurable + minecraftServer.scheduleOnMain(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0]))); // Paper + return; + } diff --git a/patches/server-unmapped/0001/0260-Add-hand-to-bucket-events.patch b/patches/server-unmapped/0001/0260-Add-hand-to-bucket-events.patch new file mode 100644 index 0000000000..a33cb57d90 --- /dev/null +++ b/patches/server-unmapped/0001/0260-Add-hand-to-bucket-events.patch @@ -0,0 +1,174 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 2 Aug 2018 08:44:35 -0500 +Subject: [PATCH] Add hand to bucket events + + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 4eda8d2065d72c67b1e1cf9e9560e13e7f24d470..0c1867c00be9ecda5294298c5b9d22098e213a81 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1658,15 +1658,17 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.getMinecraftServer().getPlayerList().sendAll(new PacketPlayOutSpawnPosition(blockposition, f)); + } + +- public BlockPosition getSpawn() { +- BlockPosition blockposition = new BlockPosition(this.worldData.a(), this.worldData.b(), this.worldData.c()); +- +- if (!this.getWorldBorder().a(blockposition)) { +- blockposition = this.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, new BlockPosition(this.getWorldBorder().getCenterX(), 0.0D, this.getWorldBorder().getCenterZ())); +- } +- +- return blockposition; +- } ++ // Paper - moved up to World ++ //public BlockPosition getSpawn() { ++ // BlockPosition blockposition = new BlockPosition(this.worldData.a(), this.worldData.b(), this.worldData.c()); ++ // ++ // if (!this.getWorldBorder().a(blockposition)) { ++ // blockposition = this.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, new BlockPosition(this.getWorldBorder().getCenterX(), 0.0D, this.getWorldBorder().getCenterZ())); ++ // } ++ // ++ // return blockposition; ++ //} ++ // Paper end + + public float v() { + return this.worldData.d(); +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityCow.java b/src/main/java/net/minecraft/world/entity/animal/EntityCow.java +index ef48ae4d398a1dd5bc67262ccdb5d8fc6bb2769c..1b43688ad232620410aa924cef02b54630ab1313 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityCow.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityCow.java +@@ -88,7 +88,7 @@ public class EntityCow extends EntityAnimal { + + if (itemstack.getItem() == Items.BUCKET && !this.isBaby()) { + // CraftBukkit start - Got milk? +- org.bukkit.event.player.PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((WorldServer) entityhuman.world, entityhuman, this.getChunkCoordinates(), this.getChunkCoordinates(), null, itemstack, Items.MILK_BUCKET); ++ org.bukkit.event.player.PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((WorldServer) entityhuman.world, entityhuman, this.getChunkCoordinates(), this.getChunkCoordinates(), null, itemstack, Items.MILK_BUCKET, enumhand); // Paper - add enumHand + + if (event.isCancelled()) { + return EnumInteractionResult.PASS; +diff --git a/src/main/java/net/minecraft/world/item/ItemBucket.java b/src/main/java/net/minecraft/world/item/ItemBucket.java +index 4bcac8defeaa146713cce43e04a51c1c9afddb1f..d126f668828e0788e369294c0b376ef52b344f2c 100644 +--- a/src/main/java/net/minecraft/world/item/ItemBucket.java ++++ b/src/main/java/net/minecraft/world/item/ItemBucket.java +@@ -71,7 +71,7 @@ public class ItemBucket extends Item { + if (iblockdata.getBlock() instanceof IFluidSource) { + // CraftBukkit start + FluidType dummyFluid = ((IFluidSource) iblockdata.getBlock()).removeFluid(DummyGeneratorAccess.INSTANCE, blockposition, iblockdata); +- PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((WorldServer) world, entityhuman, blockposition, blockposition, movingobjectpositionblock.getDirection(), itemstack, dummyFluid.a()); ++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((WorldServer) world, entityhuman, blockposition, blockposition, movingobjectpositionblock.getDirection(), itemstack, dummyFluid.a(), enumhand); // Paper - add enumhand + + if (event.isCancelled()) { + ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutBlockChange(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) +@@ -99,7 +99,7 @@ public class ItemBucket extends Item { + iblockdata = world.getType(blockposition); + BlockPosition blockposition2 = iblockdata.getBlock() instanceof IFluidContainer && this.fluidType == FluidTypes.WATER ? blockposition : blockposition1; + +- if (this.a(entityhuman, world, blockposition2, movingobjectpositionblock1, movingobjectpositionblock1.getDirection(), blockposition, itemstack)) { // CraftBukkit ++ if (this.a(entityhuman, world, blockposition2, movingobjectpositionblock1, movingobjectpositionblock1.getDirection(), blockposition, itemstack, enumhand)) { // CraftBukkit // Paper - add enumhand + this.a(world, itemstack, blockposition2); + if (entityhuman instanceof EntityPlayer) { + CriterionTriggers.y.a((EntityPlayer) entityhuman, blockposition2, itemstack); +@@ -124,10 +124,12 @@ public class ItemBucket extends Item { + public void a(World world, ItemStack itemstack, BlockPosition blockposition) {} + + public boolean a(@Nullable EntityHuman entityhuman, World world, BlockPosition blockposition, @Nullable MovingObjectPositionBlock movingobjectpositionblock) { +- return a(entityhuman, world, blockposition, movingobjectpositionblock, null, null, null); ++ // Paper start - add enumHand ++ return a(entityhuman, world, blockposition, movingobjectpositionblock, null, null, null, null); + } + +- public boolean a(EntityHuman entityhuman, World world, BlockPosition blockposition, @Nullable MovingObjectPositionBlock movingobjectpositionblock, EnumDirection enumdirection, BlockPosition clicked, ItemStack itemstack) { ++ public boolean a(EntityHuman entityhuman, World world, BlockPosition blockposition, @Nullable MovingObjectPositionBlock movingobjectpositionblock, EnumDirection enumdirection, BlockPosition clicked, ItemStack itemstack, EnumHand enumhand) { ++ // Paper end + // CraftBukkit end + if (!(this.fluidType instanceof FluidTypeFlowing)) { + return false; +@@ -140,7 +142,7 @@ public class ItemBucket extends Item { + + // CraftBukkit start + if (flag1 && entityhuman != null) { +- PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((WorldServer) world, entityhuman, blockposition, clicked, enumdirection, itemstack); ++ PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((WorldServer) world, entityhuman, blockposition, clicked, enumdirection, itemstack, enumhand); // Paper - add enumhand + if (event.isCancelled()) { + ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutBlockChange(world, blockposition)); // SPIGOT-4238: needed when looking through entity + ((EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 +@@ -149,7 +151,7 @@ public class ItemBucket extends Item { + } + // CraftBukkit end + if (!flag1) { +- return movingobjectpositionblock != null && this.a(entityhuman, world, movingobjectpositionblock.getBlockPosition().shift(movingobjectpositionblock.getDirection()), (MovingObjectPositionBlock) null, enumdirection, clicked, itemstack); // CraftBukkit ++ return movingobjectpositionblock != null && this.a(entityhuman, world, movingobjectpositionblock.getBlockPosition().shift(movingobjectpositionblock.getDirection()), (MovingObjectPositionBlock) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit // Paper - add enumhand + } else if (world.getDimensionManager().isNether() && this.fluidType.a((Tag) TagsFluid.WATER)) { + int i = blockposition.getX(); + int j = blockposition.getY(); +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index aa344e30dc7e31c49bbb842bb1bb69ecc9ba688c..7b6cdf6b1d114aa2d86267972e80d487e92e530e 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -277,6 +277,17 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + } + // Paper end + ++ // Paper start - moved up from WorldServer ++ public BlockPosition getSpawn() { ++ BlockPosition blockposition = new BlockPosition(this.worldData.a(), this.worldData.b(), this.worldData.c()); ++ ++ if (!this.getWorldBorder().a(blockposition)) { ++ blockposition = this.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, new BlockPosition(this.getWorldBorder().getCenterX(), 0.0D, this.getWorldBorder().getCenterZ())); ++ } ++ ++ return blockposition; ++ } ++ // Paper end + @Override + public boolean s_() { + 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 334ac1cb253cf3d063c7a26db698c596b425b057..d65c521b6028d61eed8cd1c63f8f9e72f2aa0e3b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -227,7 +227,7 @@ public class CraftEventFactory { + public static Entity entityDamage; // For use in EntityDamageByEntityEvent + + // helper methods +- private static boolean canBuild(WorldServer world, Player player, int x, int z) { ++ private static boolean canBuild(World world, Player player, int x, int z) { + int spawnSize = Bukkit.getServer().getSpawnRadius(); + + if (world.getDimensionKey() != World.OVERWORLD) return true; +@@ -406,6 +406,20 @@ public class CraftEventFactory { + } + + private static PlayerEvent getPlayerBucketEvent(boolean isFilling, WorldServer world, EntityHuman who, BlockPosition changed, BlockPosition clicked, EnumDirection clickedFace, ItemStack itemstack, net.minecraft.world.item.Item item) { ++ // Paper start - add enumHand ++ return getPlayerBucketEvent(isFilling, world, who, changed, clicked, clickedFace, itemstack, item, null); ++ } ++ ++ public static PlayerBucketEmptyEvent callPlayerBucketEmptyEvent(World world, EntityHuman who, BlockPosition changed, BlockPosition clicked, EnumDirection clickedFace, ItemStack itemstack, EnumHand enumHand) { ++ return (PlayerBucketEmptyEvent) getPlayerBucketEvent(false, world, who, changed, clicked, clickedFace, itemstack, Items.BUCKET, enumHand); ++ } ++ ++ public static PlayerBucketFillEvent callPlayerBucketFillEvent(World world, EntityHuman who, BlockPosition changed, BlockPosition clicked, EnumDirection clickedFace, ItemStack itemInHand, net.minecraft.world.item.Item bucket, EnumHand enumHand) { ++ return (PlayerBucketFillEvent) getPlayerBucketEvent(true, world, who, clicked, changed, clickedFace, itemInHand, bucket, enumHand); ++ } ++ ++ private static PlayerEvent getPlayerBucketEvent(boolean isFilling, World world, EntityHuman who, BlockPosition changed, BlockPosition clicked, EnumDirection clickedFace, ItemStack itemstack, net.minecraft.world.item.Item item, EnumHand enumHand) { ++ // Paper end + Player player = (Player) who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asNewCraftStack(item); + Material bucket = CraftMagicNumbers.getMaterial(itemstack.getItem()); +@@ -418,10 +432,10 @@ public class CraftEventFactory { + + PlayerEvent event; + if (isFilling) { +- event = new PlayerBucketFillEvent(player, block, blockClicked, blockFace, bucket, itemInHand); ++ event = new PlayerBucketFillEvent(player, block, blockClicked, blockFace, bucket, itemInHand, enumHand == null ? null : enumHand == EnumHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND); // Paper - add enumHand + ((PlayerBucketFillEvent) event).setCancelled(!canBuild(world, player, changed.getX(), changed.getZ())); + } else { +- event = new PlayerBucketEmptyEvent(player, block, blockClicked, blockFace, bucket, itemInHand); ++ event = new PlayerBucketEmptyEvent(player, block, blockClicked, blockFace, bucket, itemInHand, enumHand == null ? null : enumHand == EnumHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND); // Paper - add enumHand + ((PlayerBucketEmptyEvent) event).setCancelled(!canBuild(world, player, changed.getX(), changed.getZ())); + } + diff --git a/patches/server-unmapped/0001/0261-MC-135506-Experience-should-save-as-Integers.patch b/patches/server-unmapped/0001/0261-MC-135506-Experience-should-save-as-Integers.patch new file mode 100644 index 0000000000..7c85de6290 --- /dev/null +++ b/patches/server-unmapped/0001/0261-MC-135506-Experience-should-save-as-Integers.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 3 Aug 2018 00:04:54 -0400 +Subject: [PATCH] MC-135506: Experience should save as Integers + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java b/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java +index 3387a19044b3ee2a1ef549c328c8bc354a5b6d23..a7551e95185895a290be70d501496279eaf884ae 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/EntityExperienceOrb.java +@@ -218,7 +218,7 @@ public class EntityExperienceOrb extends Entity { + public void saveData(NBTTagCompound nbttagcompound) { + nbttagcompound.setShort("Health", (short) this.e); + nbttagcompound.setShort("Age", (short) this.c); +- nbttagcompound.setShort("Value", (short) this.value); ++ nbttagcompound.setInt("Value", this.value); // Paper - save as Integer + this.savePaperNBT(nbttagcompound); // Paper + } + +@@ -226,7 +226,7 @@ public class EntityExperienceOrb extends Entity { + public void loadData(NBTTagCompound nbttagcompound) { + this.e = nbttagcompound.getShort("Health"); + this.c = nbttagcompound.getShort("Age"); +- this.value = nbttagcompound.getShort("Value"); ++ this.value = nbttagcompound.getInt("Value"); // Paper - load as Integer + this.loadPaperNBT(nbttagcompound); // Paper + } + diff --git a/patches/server-unmapped/0001/0262-Fix-client-rendering-skulls-from-same-user.patch b/patches/server-unmapped/0001/0262-Fix-client-rendering-skulls-from-same-user.patch new file mode 100644 index 0000000000..192b8c8a60 --- /dev/null +++ b/patches/server-unmapped/0001/0262-Fix-client-rendering-skulls-from-same-user.patch @@ -0,0 +1,147 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 22 Nov 2016 00:40:42 -0500 +Subject: [PATCH] Fix client rendering skulls from same user + +See: https://github.com/PaperMC/Paper/issues/1304 + +Changes the UUID sent to client to be based on either +the texture payload, or random. + +This allows the client to render multiple skull textures from the same user, +for when different skins were used when skull was made. + +diff --git a/src/main/java/net/minecraft/network/PacketDataSerializer.java b/src/main/java/net/minecraft/network/PacketDataSerializer.java +index df459918c14589155a574730205cb35d463b8079..5a1187b001004afe22d208bc5d7c288e796e16a6 100644 +--- a/src/main/java/net/minecraft/network/PacketDataSerializer.java ++++ b/src/main/java/net/minecraft/network/PacketDataSerializer.java +@@ -37,6 +37,7 @@ import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.resources.MinecraftKey; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.block.entity.TileEntitySkull; + import net.minecraft.world.phys.MovingObjectPositionBlock; + import net.minecraft.world.phys.Vec3D; + +@@ -311,9 +312,18 @@ public class PacketDataSerializer extends ByteBuf { + if (item.usesDurability() || item.n()) { + // Spigot start - filter + itemstack = itemstack.cloneItemStack(); +- CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); ++ //CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); // Paper - This is no longer needed due to NBT being supported + // Spigot end + nbttagcompound = itemstack.getTag(); ++ // Paper start ++ if (nbttagcompound != null && nbttagcompound.hasKeyOfType("SkullOwner", 10)) { ++ NBTTagCompound owner = nbttagcompound.getCompound("SkullOwner"); ++ if (owner.hasUUID("Id")) { ++ nbttagcompound.setUUID("SkullOwnerOrig", owner.getUUID("Id")); ++ TileEntitySkull.sanitizeUUID(owner); ++ } ++ } ++ // Paper end + } + + this.a(nbttagcompound); +@@ -333,7 +343,16 @@ public class PacketDataSerializer extends ByteBuf { + itemstack.setTag(this.l()); + // CraftBukkit start + if (itemstack.getTag() != null) { +- CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); ++ // Paper start - Fix skulls of same owner - restore orig ID since we changed it on send to client ++ if (itemstack.tag.hasKey("SkullOwnerOrig")) { ++ NBTTagCompound owner = itemstack.tag.getCompound("SkullOwner"); ++ if (itemstack.tag.hasKey("SkullOwnerOrig")) { ++ owner.map.put("Id", itemstack.tag.map.get("SkullOwnerOrig")); ++ itemstack.tag.remove("SkullOwnerOrig"); ++ } ++ } ++ // Paper end ++ // CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); // Paper - This is no longer needed due to NBT being supported + } + // CraftBukkit end + return itemstack; +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +index b6b55d5baa5e8a6b69a3e4865c06bc8a4d61a4f3..152118729b1a95dcae05d32aa4289034ba394226 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +@@ -15,6 +15,7 @@ import net.minecraft.network.PacketDataSerializer; + import net.minecraft.network.protocol.Packet; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.entity.TileEntitySkull; + import net.minecraft.world.level.chunk.BiomeStorage; + import net.minecraft.world.level.chunk.Chunk; + import net.minecraft.world.level.chunk.ChunkSection; +@@ -69,6 +70,7 @@ public class PacketPlayOutMapChunk implements Packet { + + if (this.f() || (i & 1 << j) != 0) { + NBTTagCompound nbttagcompound = tileentity.b(); ++ if (tileentity instanceof TileEntitySkull) { TileEntitySkull.sanitizeTileEntityUUID(nbttagcompound); } // Paper + + this.g.add(nbttagcompound); + } +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index c525afbc7d73488db2cae1501cdbe80ec05aeb7c..ce5d8463763dd39e1225d9dec0514b1754df5411 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -114,7 +114,7 @@ public final class ItemStack { + private int g; + @Deprecated + private Item item; +- private NBTTagCompound tag; ++ public NBTTagCompound tag; // Paper private -> public + private boolean j; + private Entity k; + private ShapeDetectorBlock l; +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySkull.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySkull.java +index 22217f24b4a87f10b6d5a3e37d23a1164af84ace..4f7c014fa609a39cac651ccc6d3397d7edb77d8d 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySkull.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySkull.java +@@ -9,6 +9,7 @@ import java.util.UUID; + import javax.annotation.Nullable; + import net.minecraft.nbt.GameProfileSerializer; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; + import net.minecraft.network.protocol.game.PacketPlayOutTileEntityData; + import net.minecraft.server.players.UserCache; + import net.minecraft.util.UtilColor; +@@ -154,9 +155,37 @@ public class TileEntitySkull extends TileEntity /*implements ITickable*/ { // Pa + @Nullable + @Override + public PacketPlayOutTileEntityData getUpdatePacket() { +- return new PacketPlayOutTileEntityData(this.position, 4, this.b()); ++ return new PacketPlayOutTileEntityData(this.position, 4, sanitizeTileEntityUUID(this.b())); // Paper + } + ++ // Paper start ++ public static NBTTagCompound sanitizeTileEntityUUID(NBTTagCompound cmp) { ++ NBTTagCompound owner = cmp.getCompound("Owner"); ++ if (!owner.isEmpty()) { ++ sanitizeUUID(owner); ++ } ++ return cmp; ++ } ++ ++ public static void sanitizeUUID(NBTTagCompound owner) { ++ NBTTagCompound properties = owner.getCompound("Properties"); ++ NBTTagList list = null; ++ if (!properties.isEmpty()) { ++ list = properties.getList("textures", 10); ++ } ++ ++ if (list != null && !list.isEmpty()) { ++ String textures = ((NBTTagCompound)list.get(0)).getString("Value"); ++ if (textures != null && textures.length() > 3) { ++ UUID uuid = UUID.nameUUIDFromBytes(textures.getBytes()); ++ owner.setUUID("Id", uuid); ++ return; ++ } ++ } ++ owner.setUUID("Id", UUID.randomUUID()); ++ } ++ // Paper end ++ + @Override + public NBTTagCompound b() { + return this.save(new NBTTagCompound()); diff --git a/patches/server-unmapped/0001/0263-Add-Early-Warning-Feature-to-WatchDog.patch b/patches/server-unmapped/0001/0263-Add-Early-Warning-Feature-to-WatchDog.patch new file mode 100644 index 0000000000..7a28855e07 --- /dev/null +++ b/patches/server-unmapped/0001/0263-Add-Early-Warning-Feature-to-WatchDog.patch @@ -0,0 +1,184 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index bd508025b771424c942fd856c31d520b6f548082..62621562137cba4804f0465c58d25ca2786328e5 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -25,6 +25,7 @@ import org.bukkit.configuration.file.YamlConfiguration; + import co.aikar.timings.Timings; + import co.aikar.timings.TimingsManager; + import org.spigotmc.SpigotConfig; ++import org.spigotmc.WatchdogThread; + + public class PaperConfig { + +@@ -297,6 +298,14 @@ public class PaperConfig { + } + } + ++ public static int watchdogPrintEarlyWarningEvery = 5000; ++ public static int watchdogPrintEarlyWarningDelay = 10000; ++ private static void watchdogEarlyWarning() { ++ watchdogPrintEarlyWarningEvery = getInt("settings.watchdog.early-warning-every", 5000); ++ watchdogPrintEarlyWarningDelay = getInt("settings.watchdog.early-warning-delay", 10000); ++ WatchdogThread.doStart(SpigotConfig.timeoutTime, SpigotConfig.restartOnCrash ); ++ } ++ + public static int tabSpamIncrement = 1; + public static int tabSpamLimit = 500; + private static void tabSpamLimiters() { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index dbf5a849358158324e8a5c87f831236b71f7ec0d..1733717b7cae3e7a805e5275ff89967744c4bc4a 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1018,6 +1018,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0 && monotonicMillis() > lastTick + timeoutTime && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable ++ // Paper start ++ Logger log = Bukkit.getServer().getLogger(); ++ long currentTime = monotonicMillis(); ++ if ( lastTick != 0 && timeoutTime > 0 && currentTime > lastTick + earlyWarningEvery && !Boolean.getBoolean("disable.watchdog") ) + { +- 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" ); +@@ -93,29 +108,46 @@ 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 + 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 ) + { + 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 ( 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 ) + { + interrupt(); diff --git a/patches/server-unmapped/0001/0264-Make-EnderDragon-implement-Mob.patch b/patches/server-unmapped/0001/0264-Make-EnderDragon-implement-Mob.patch new file mode 100644 index 0000000000..cce5f2ddd2 --- /dev/null +++ b/patches/server-unmapped/0001/0264-Make-EnderDragon-implement-Mob.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 10 Aug 2018 22:11:49 -0400 +Subject: [PATCH] Make EnderDragon implement Mob + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexLivingEntity.java +index 3550eb81b66cad3cdfa41ddb5bb554b541cbbfe1..b537732070c784a97a96ccb77c211646b00580cc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexLivingEntity.java +@@ -1,17 +1,18 @@ + package org.bukkit.craftbukkit.entity; + ++import net.minecraft.world.entity.EntityInsentient; // Paper + import net.minecraft.world.entity.EntityLiving; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.ComplexLivingEntity; + +-public abstract class CraftComplexLivingEntity extends CraftLivingEntity implements ComplexLivingEntity { +- public CraftComplexLivingEntity(CraftServer server, EntityLiving entity) { ++public abstract class CraftComplexLivingEntity extends CraftMob implements ComplexLivingEntity { // Paper ++ public CraftComplexLivingEntity(CraftServer server, EntityInsentient entity) { // Paper + super(server, entity); + } + + @Override +- public EntityLiving getHandle() { +- return (EntityLiving) entity; ++ public EntityInsentient getHandle() { // Paper ++ return (EntityInsentient) entity; // Paper + } + + @Override diff --git a/patches/server-unmapped/0001/0265-Use-ConcurrentHashMap-in-JsonList.patch b/patches/server-unmapped/0001/0265-Use-ConcurrentHashMap-in-JsonList.patch new file mode 100644 index 0000000000..bf200d6b87 --- /dev/null +++ b/patches/server-unmapped/0001/0265-Use-ConcurrentHashMap-in-JsonList.patch @@ -0,0 +1,136 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: egg82 +Date: Tue, 7 Aug 2018 01:24:23 -0600 +Subject: [PATCH] Use ConcurrentHashMap in JsonList + +This is specifically aimed at fixing #471 + +Using a ConcurrentHashMap because thread safety +The performance benefit of Map over ConcurrentMap is negligabe at best in this scenaio, as most operations will be get and not add or remove +Even without considering the use-case the benefits are still negligable + +Original ideas for the system included an expiration policy and/or handler +The simpler solution was to use a computeIfPresent in the get method +This will simultaneously have an O(1) lookup time and automatically expire any values +Since the get method (nor other similar methods) don't seem to have a critical need to flush the map to disk at any of these points further processing is simply wasteful +Meaning the original function expired values unrelated to the current value without actually having any explicit need to + +The h method was heavily modified to be much more efficient in its processing +Also instead of being called on every get, it's now called just before a save +This will eliminate stale values being flushed to disk + +Modified isEmpty to use the isEmpty() method instead of the slightly confusing size() < 1 +The point of this is readability, but does have a side-benefit of a small microptimization + +Finally, added a couple obfhelpers for the modified code + +diff --git a/src/main/java/net/minecraft/server/players/JsonList.java b/src/main/java/net/minecraft/server/players/JsonList.java +index 52256f72b00d3b868ef1a60e15a3836197c769d9..cd35b833d3047a38be980ee550641e87bd3b9b01 100644 +--- a/src/main/java/net/minecraft/server/players/JsonList.java ++++ b/src/main/java/net/minecraft/server/players/JsonList.java +@@ -12,6 +12,8 @@ import java.io.BufferedReader; + import java.io.BufferedWriter; + import java.io.File; + import java.io.IOException; ++import java.lang.reflect.ParameterizedType; // Paper ++import java.lang.reflect.Type; // Paper + import java.nio.charset.StandardCharsets; + import java.util.Collection; + import java.util.Iterator; +@@ -28,7 +30,22 @@ public abstract class JsonList> { + protected static final Logger LOGGER = LogManager.getLogger(); + private static final Gson b = (new GsonBuilder()).setPrettyPrinting().create(); + private final File c; +- private final Map d = Maps.newHashMap(); ++ // Paper - replace HashMap is ConcurrentHashMap ++ private final Map d = Maps.newConcurrentMap(); private final Map getBackingMap() { return this.d; } // Paper - OBFHELPER ++ private boolean e = true; ++ private static final ParameterizedType f = new ParameterizedType() { ++ public Type[] getActualTypeArguments() { ++ return new Type[]{JsonListEntry.class}; ++ } ++ ++ public Type getRawType() { ++ return List.class; ++ } ++ ++ public Type getOwnerType() { ++ return null; ++ } ++ }; + + public JsonList(File file) { + this.c = file; +@@ -51,8 +68,13 @@ public abstract class JsonList> { + + @Nullable + public V get(K k0) { +- this.g(); +- return (V) this.d.get(this.a(k0)); // CraftBukkit - fix decompile error ++ // Paper start ++ // this.g(); ++ // return (V) this.d.get(this.a(k0)); // CraftBukkit - fix decompile error ++ return (V) this.getBackingMap().computeIfPresent(this.getMappingKey(k0), (k, v) -> { ++ return v.hasExpired() ? null : v; ++ }); ++ // Paper end + } + + public void remove(K k0) { +@@ -81,9 +103,11 @@ public abstract class JsonList> { + // CraftBukkit end + + public boolean isEmpty() { +- return this.d.size() < 1; ++ // return this.d.size() < 1; // Paper ++ return this.getBackingMap().isEmpty(); // Paper - readability is the goal. As an aside, isEmpty() uses only sumCount() and a comparison. size() uses sumCount(), casts, and boolean logic + } + ++ protected final String getMappingKey(K k0) { return a(k0); } // Paper - OBFHELPER + protected String a(K k0) { + return k0.toString(); + } +@@ -92,8 +116,9 @@ public abstract class JsonList> { + return this.d.containsKey(this.a(k0)); + } + ++ private void removeStaleEntries() { g(); } // Paper - OBFHELPER + private void g() { +- List list = Lists.newArrayList(); ++ /*List list = Lists.newArrayList(); + Iterator iterator = this.d.values().iterator(); + + while (iterator.hasNext()) { +@@ -110,8 +135,10 @@ public abstract class JsonList> { + K k0 = (K) iterator.next(); // CraftBukkit - decompile error + + this.d.remove(this.a(k0)); +- } ++ }*/ + ++ this.getBackingMap().values().removeIf(JsonListEntry::hasExpired); ++ // Paper end + } + + protected abstract JsonListEntry a(JsonObject jsonobject); +@@ -121,6 +148,7 @@ public abstract class JsonList> { + } + + public void save() throws IOException { ++ this.removeStaleEntries(); // Paper - remove expired values before saving + JsonArray jsonarray = new JsonArray(); + + this.d.values().stream().map((jsonlistentry) -> { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 95cadb09b5a154d7dfe8144fab6c403547672287..1faae8a451c25cc8e37ef1907206a4f721477b13 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -618,7 +618,7 @@ public abstract class PlayerList { + } else if (!this.isWhitelisted(gameprofile, event)) { // Paper + //chatmessage = new ChatMessage("multiplayer.disconnect.not_whitelisted"); // Paper + //event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, org.spigotmc.SpigotConfig.whitelistMessage); // Spigot // Paper - moved to isWhitelisted +- } else if (getIPBans().isBanned(socketaddress) && !getIPBans().get(socketaddress).hasExpired()) { ++ } else if (getIPBans().isBanned(socketaddress) && getIPBans().get(socketaddress) != null && !getIPBans().get(socketaddress).hasExpired()) { // Paper - fix NPE with temp ip bans + IpBanEntry ipbanentry = this.l.get(socketaddress); + + chatmessage = new ChatMessage("multiplayer.disconnect.banned_ip.reason", new Object[]{ipbanentry.getReason()}); diff --git a/patches/server-unmapped/0001/0266-Use-a-Queue-for-Queueing-Commands.patch b/patches/server-unmapped/0001/0266-Use-a-Queue-for-Queueing-Commands.patch new file mode 100644 index 0000000000..eead0924bc --- /dev/null +++ b/patches/server-unmapped/0001/0266-Use-a-Queue-for-Queueing-Commands.patch @@ -0,0 +1,33 @@ +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 a0804c4df6f047cf913ae70970219617052e853f..2d42b863b3fd83d1ee0532d1fcb63861641ec47b 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -75,7 +75,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + + private static final Logger LOGGER = LogManager.getLogger(); + private static final Pattern k = Pattern.compile("^[a-fA-F0-9]{40}$"); +- private final List serverCommandQueue = Collections.synchronizedList(Lists.newArrayList()); ++ private final java.util.Queue serverCommandQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - use a proper queue + private RemoteStatusListener remoteStatusListener; + public final RemoteControlCommandListener remoteControlCommandListener; + private RemoteControlListener remoteControlListener; +@@ -434,8 +434,10 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + + public void handleCommandQueue() { + MinecraftTimings.serverCommandTimer.startTiming(); // Spigot +- while (!this.serverCommandQueue.isEmpty()) { +- ServerCommand servercommand = (ServerCommand) this.serverCommandQueue.remove(0); ++ // Paper start - use proper queue ++ ServerCommand servercommand; ++ while ((servercommand = this.serverCommandQueue.poll()) != null) { ++ // Paper end + + // CraftBukkit start - ServerCommand for preprocessing + ServerCommandEvent event = new ServerCommandEvent(console, servercommand.command); diff --git a/patches/server-unmapped/0001/0267-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch b/patches/server-unmapped/0001/0267-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch new file mode 100644 index 0000000000..2e0e968f6b --- /dev/null +++ b/patches/server-unmapped/0001/0267-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch @@ -0,0 +1,73 @@ +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 Tile 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 32e4e59767587455272e685dfd23f945ba05f976..a8e94f69faec93661dc6ae2efeec44b8bfd2e965 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -3,8 +3,10 @@ package org.bukkit.craftbukkit; + import com.google.common.base.Preconditions; + import com.google.common.base.Predicates; + import java.lang.ref.WeakReference; ++import java.util.ArrayList; + import java.util.Arrays; + import java.util.Collection; ++import java.util.List; + import java.util.function.Predicate; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.IRegistry; +@@ -130,9 +132,16 @@ public class CraftChunk implements Chunk { + + @Override + public BlockState[] getTileEntities() { ++ // Paper start ++ return getTileEntities(true); ++ } ++ ++ @Override ++ public BlockState[] getTileEntities(boolean useSnapshot) { + if (!isLoaded()) { + getWorld().getChunkAt(x, z); // Transient load for this tick + } ++ // Paper end + int index = 0; + net.minecraft.world.level.chunk.Chunk chunk = getHandle(); + +@@ -144,11 +153,33 @@ public class CraftChunk implements Chunk { + } + + BlockPosition position = (BlockPosition) obj; +- entities[index++] = worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(); ++ entities[index++] = worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(useSnapshot); // Paper ++ } ++ ++ return entities; ++ } ++ ++ // Paper start ++ @Override ++ public Collection getTileEntities(Predicate blockPredicate, boolean useSnapshot) { ++ Preconditions.checkNotNull(blockPredicate, "blockPredicate"); ++ if (!isLoaded()) { ++ getWorld().getChunkAt(x, z); // Transient load for this tick ++ } ++ net.minecraft.world.level.chunk.Chunk chunk = getHandle(); ++ ++ List entities = new ArrayList<>(); ++ ++ for (BlockPosition position : chunk.tileEntities.keySet()) { ++ Block block = worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()); ++ if (blockPredicate.test(block)) { ++ entities.add(block.getState(useSnapshot)); ++ } + } + + return entities; + } ++ // Paper end + + @Override + public boolean isLoaded() { diff --git a/patches/server-unmapped/0001/0268-Allow-disabling-armour-stand-ticking.patch b/patches/server-unmapped/0001/0268-Allow-disabling-armour-stand-ticking.patch new file mode 100644 index 0000000000..788112651f --- /dev/null +++ b/patches/server-unmapped/0001/0268-Allow-disabling-armour-stand-ticking.patch @@ -0,0 +1,159 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Wed, 15 Aug 2018 01:26:09 -0700 +Subject: [PATCH] Allow disabling armour stand ticking + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3562950df4868b1393790b1a1ff1fe0dc589c155..5ab0e7183e48134b7a0f736462516b1a8a333b04 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -384,4 +384,10 @@ public class PaperWorldConfig { + private void armorStandEntityLookups() { + armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", true); + } ++ ++ public boolean armorStandTick = true; ++ private void armorStandTick() { ++ this.armorStandTick = this.getBoolean("armor-stands-tick", this.armorStandTick); ++ log("ArmorStand ticking is " + (this.armorStandTick ? "enabled" : "disabled") + " by default"); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +index e97d25339b37a70f91022dcb021bbe82fb8f5eda..8d35240405d7f7245f3c7b0b611973d58fa4384f 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +@@ -81,9 +81,16 @@ public class EntityArmorStand extends EntityLiving { + public Vector3f leftLegPose; + public Vector3f rightLegPose; + public boolean canMove = true; // Paper ++ // Paper start - Allow ArmorStands not to tick ++ public boolean canTick = true; ++ public boolean canTickSetByAPI = false; ++ private boolean noTickPoseDirty = false; ++ private boolean noTickEquipmentDirty = false; ++ // Paper end + + public EntityArmorStand(EntityTypes entitytypes, World world) { + super(entitytypes, world); ++ if (world != null) this.canTick = world.paperConfig.armorStandTick; // Paper - armour stand ticking + this.handItems = NonNullList.a(2, ItemStack.b); + this.armorItems = NonNullList.a(4, ItemStack.b); + this.headPose = EntityArmorStand.bj; +@@ -179,6 +186,7 @@ public class EntityArmorStand extends EntityLiving { + this.armorItems.set(enumitemslot.b(), itemstack); + } + ++ this.noTickEquipmentDirty = true; // Paper - Allow equipment to be updated even when tick disabled + } + + @Override +@@ -259,6 +267,7 @@ public class EntityArmorStand extends EntityLiving { + } + + nbttagcompound.set("Pose", this.B()); ++ if (this.canTickSetByAPI) nbttagcompound.setBoolean("Paper.CanTickOverride", this.canTick); // Paper - persist no tick setting + } + + @Override +@@ -290,6 +299,12 @@ public class EntityArmorStand extends EntityLiving { + this.setBasePlate(nbttagcompound.getBoolean("NoBasePlate")); + this.setMarker(nbttagcompound.getBoolean("Marker")); + this.noclip = !this.A(); ++ // Paper start - persist no tick ++ if (nbttagcompound.hasKey("Paper.CanTickOverride")) { ++ this.canTick = nbttagcompound.getBoolean("Paper.CanTickOverride"); ++ this.canTickSetByAPI = true; ++ } ++ // Paper end + NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Pose"); + + this.g(nbttagcompound1); +@@ -645,7 +660,29 @@ public class EntityArmorStand extends EntityLiving { + + @Override + public void tick() { ++ // Paper start ++ if (!this.canTick) { ++ if (this.noTickPoseDirty) { ++ this.noTickPoseDirty = false; ++ this.updatePose(); ++ } ++ ++ if (this.noTickEquipmentDirty) { ++ this.noTickEquipmentDirty = false; ++ this.updateEquipment(); ++ } ++ ++ return; ++ } ++ // Paper end ++ + super.tick(); ++ // Paper start - Split into separate method ++ updatePose(); ++ } ++ ++ public void updatePose() { ++ // Paper end + Vector3f vector3f = (Vector3f) this.datawatcher.get(EntityArmorStand.c); + + if (!this.headPose.equals(vector3f)) { +@@ -768,29 +805,36 @@ public class EntityArmorStand extends EntityLiving { + public void setHeadPose(Vector3f vector3f) { + this.headPose = vector3f; + this.datawatcher.set(EntityArmorStand.c, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setBodyPose(Vector3f vector3f) { + this.bodyPose = vector3f; + this.datawatcher.set(EntityArmorStand.d, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setLeftArmPose(Vector3f vector3f) { + this.leftArmPose = vector3f; + this.datawatcher.set(EntityArmorStand.e, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setRightArmPose(Vector3f vector3f) { + this.rightArmPose = vector3f; + this.datawatcher.set(EntityArmorStand.f, vector3f); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setLeftLegPose(Vector3f vector3f) { ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + this.leftLegPose = vector3f; + this.datawatcher.set(EntityArmorStand.g, vector3f); ++ + } + + public void setRightLegPose(Vector3f vector3f) { ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + this.rightLegPose = vector3f; + this.datawatcher.set(EntityArmorStand.bh, vector3f); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +index 103f935d9b7a2cbe9639528c587d8ac2e5f14d07..348993f3839f984be65daaf87f3510865e8e4670 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +@@ -313,5 +313,16 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + public boolean isSlotDisabled(org.bukkit.inventory.EquipmentSlot slot) { + return getHandle().isSlotDisabled(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); + } ++ ++ @Override ++ public boolean canTick() { ++ return this.getHandle().canTick; ++ } ++ ++ @Override ++ public void setCanTick(final boolean tick) { ++ this.getHandle().canTick = tick; ++ this.getHandle().canTickSetByAPI = true; ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0269-Optimize-BlockPosition-helper-methods.patch b/patches/server-unmapped/0001/0269-Optimize-BlockPosition-helper-methods.patch new file mode 100644 index 0000000000..d248d915c6 --- /dev/null +++ b/patches/server-unmapped/0001/0269-Optimize-BlockPosition-helper-methods.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 15 Aug 2018 12:05:12 -0700 +Subject: [PATCH] Optimize BlockPosition helper methods + +Resolves #1338 + +diff --git a/src/main/java/net/minecraft/core/BlockPosition.java b/src/main/java/net/minecraft/core/BlockPosition.java +index 370b2c4460d6b52b5ef7da89f5ebf7ef50deb582..4c9ec211470f95d538d1d95c74796190edf99b87 100644 +--- a/src/main/java/net/minecraft/core/BlockPosition.java ++++ b/src/main/java/net/minecraft/core/BlockPosition.java +@@ -121,58 +121,75 @@ public class BlockPosition extends BaseBlockPosition { + + @Override + public BlockPosition up() { +- return this.shift(EnumDirection.UP); ++ return new BlockPosition(this.getX(), this.getY() + 1, this.getZ()); // Paper - Optimize BlockPosition + } + + @Override + public BlockPosition up(int i) { +- return this.shift(EnumDirection.UP, i); ++ return i == 0 ? this : new BlockPosition(this.getX(), this.getY() + i, this.getZ()); // Paper - Optimize BlockPosition + } + + @Override + public BlockPosition down() { +- return this.shift(EnumDirection.DOWN); ++ return new BlockPosition(this.getX(), this.getY() - 1, this.getZ()); // Paper - Optimize BlockPosition + } + + @Override + public BlockPosition down(int i) { +- return this.shift(EnumDirection.DOWN, i); ++ return i == 0 ? this : new BlockPosition(this.getX(), this.getY() - i, this.getZ()); // Paper - Optimize BlockPosition + } + + public BlockPosition north() { +- return this.shift(EnumDirection.NORTH); ++ return new BlockPosition(this.getX(), this.getY(), this.getZ() - 1); // Paper - Optimize BlockPosition + } + + public BlockPosition north(int i) { +- return this.shift(EnumDirection.NORTH, i); ++ return i == 0 ? this : new BlockPosition(this.getX(), this.getY(), this.getZ() - i); // Paper - Optimize BlockPosition + } + + public BlockPosition south() { +- return this.shift(EnumDirection.SOUTH); ++ return new BlockPosition(this.getX(), this.getY(), this.getZ() + 1); // Paper - Optimize BlockPosition + } + + public BlockPosition south(int i) { +- return this.shift(EnumDirection.SOUTH, i); ++ return i == 0 ? this : new BlockPosition(this.getX(), this.getY(), this.getZ() + i); // Paper - Optimize BlockPosition + } + + public BlockPosition west() { +- return this.shift(EnumDirection.WEST); ++ return new BlockPosition(this.getX() - 1, this.getY(), this.getZ()); // Paper - Optimize BlockPosition + } + + public BlockPosition west(int i) { +- return this.shift(EnumDirection.WEST, i); ++ return i == 0 ? this : new BlockPosition(this.getX() - i, this.getY(), this.getZ()); // Paper - Optimize BlockPosition + } + + public BlockPosition east() { +- return this.shift(EnumDirection.EAST); ++ return new BlockPosition(this.getX() + 1, this.getY(), this.getZ()); // Paper - Optimize BlockPosition + } + + public BlockPosition east(int i) { +- return this.shift(EnumDirection.EAST, i); ++ return i == 0 ? this : new BlockPosition(this.getX() + i, this.getY(), this.getZ()); // Paper - Optimize BlockPosition + } + + public BlockPosition shift(EnumDirection enumdirection) { +- return new BlockPosition(this.getX() + enumdirection.getAdjacentX(), this.getY() + enumdirection.getAdjacentY(), this.getZ() + enumdirection.getAdjacentZ()); ++ // Paper Start - Optimize BlockPosition ++ switch(enumdirection) { ++ case UP: ++ return new BlockPosition(this.getX(), this.getY() + 1, this.getZ()); ++ case DOWN: ++ return new BlockPosition(this.getX(), this.getY() - 1, this.getZ()); ++ case NORTH: ++ return new BlockPosition(this.getX(), this.getY(), this.getZ() - 1); ++ case SOUTH: ++ return new BlockPosition(this.getX(), this.getY(), this.getZ() + 1); ++ case WEST: ++ return new BlockPosition(this.getX() - 1, this.getY(), this.getZ()); ++ case EAST: ++ return new BlockPosition(this.getX() + 1, this.getY(), this.getZ()); ++ default: ++ return new BlockPosition(this.getX() + enumdirection.getAdjacentX(), this.getY() + enumdirection.getAdjacentY(), this.getZ() + enumdirection.getAdjacentZ()); ++ } ++ // Paper End + } + + @Override diff --git a/patches/server-unmapped/0001/0270-Restore-vanlla-default-mob-spawn-range.patch b/patches/server-unmapped/0001/0270-Restore-vanlla-default-mob-spawn-range.patch new file mode 100644 index 0000000000..9574b666a3 --- /dev/null +++ b/patches/server-unmapped/0001/0270-Restore-vanlla-default-mob-spawn-range.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 18 Aug 2018 12:43:16 -0400 +Subject: [PATCH] Restore vanlla default mob-spawn-range + + +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 0efcbab8f8806aeb8dd8bd6384e5a7cee375d100..34ee684901906fc2ef5f0d09680d2686b813e52b 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -165,7 +165,7 @@ public class SpigotWorldConfig + public byte mobSpawnRange; + private void mobSpawnRange() + { +- mobSpawnRange = (byte) getInt( "mob-spawn-range", 6 ); ++ mobSpawnRange = (byte) getInt( "mob-spawn-range", 8 ); // Paper - Vanilla + log( "Mob Spawn Range: " + mobSpawnRange ); + } + diff --git a/patches/server-unmapped/0001/0271-Slime-Pathfinder-Events.patch b/patches/server-unmapped/0001/0271-Slime-Pathfinder-Events.patch new file mode 100644 index 0000000000..c9e838ba63 --- /dev/null +++ b/patches/server-unmapped/0001/0271-Slime-Pathfinder-Events.patch @@ -0,0 +1,167 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 24 Aug 2018 08:18:42 -0500 +Subject: [PATCH] Slime Pathfinder Events + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java b/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java +index 01d5b0db9a34d88172e8c7c84c4e1d0b2562217c..40e39e382092b1a8f831da0cea1557a781c98600 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java +@@ -45,6 +45,14 @@ import net.minecraft.world.level.levelgen.SeededRandom; + import net.minecraft.world.level.storage.loot.LootTables; + import net.minecraft.world.phys.Vec3D; + ++// Paper start ++import com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent; ++import com.destroystokyo.paper.event.entity.SlimeSwimEvent; ++import com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent; ++import com.destroystokyo.paper.event.entity.SlimeWanderEvent; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.entity.Slime; ++// Paper end + // CraftBukkit start + import java.util.ArrayList; + import java.util.List; +@@ -105,6 +113,7 @@ public class EntitySlime extends EntityInsentient implements IMonster { + @Override + public void saveData(NBTTagCompound nbttagcompound) { + super.saveData(nbttagcompound); ++ nbttagcompound.setBoolean("Paper.canWander", this.canWander); // Paper + nbttagcompound.setInt("Size", this.getSize() - 1); + nbttagcompound.setBoolean("wasOnGround", this.bp); + } +@@ -119,6 +128,11 @@ public class EntitySlime extends EntityInsentient implements IMonster { + + this.setSize(i + 1, false); + super.loadData(nbttagcompound); ++ // Paper start - check exists before loading or this will be loaded as false ++ if (nbttagcompound.hasKey("Paper.canWander")) { ++ this.canWander = nbttagcompound.getBoolean("Paper.canWander"); ++ } ++ // Paper end + this.bp = nbttagcompound.getBoolean("wasOnGround"); + } + +@@ -398,7 +412,7 @@ public class EntitySlime extends EntityInsentient implements IMonster { + + @Override + public boolean a() { +- return !this.a.isPassenger(); ++ return !this.a.isPassenger() && this.a.canWander && new SlimeWanderEvent((Slime) this.a.getBukkitEntity()).callEvent(); // Paper + } + + @Override +@@ -419,7 +433,7 @@ public class EntitySlime extends EntityInsentient implements IMonster { + + @Override + public boolean a() { +- return (this.a.isInWater() || this.a.aQ()) && this.a.getControllerMove() instanceof EntitySlime.ControllerMoveSlime; ++ return (this.a.isInWater() || this.a.aQ()) && this.a.getControllerMove() instanceof EntitySlime.ControllerMoveSlime && this.a.canWander && new SlimeSwimEvent((Slime) this.a.getBukkitEntity()).callEvent(); // Paper + } + + @Override +@@ -445,14 +459,18 @@ public class EntitySlime extends EntityInsentient implements IMonster { + + @Override + public boolean a() { +- return this.a.getGoalTarget() == null && (this.a.onGround || this.a.isInWater() || this.a.aQ() || this.a.hasEffect(MobEffects.LEVITATION)) && this.a.getControllerMove() instanceof EntitySlime.ControllerMoveSlime; ++ return this.a.getGoalTarget() == null && (this.a.onGround || this.a.isInWater() || this.a.aQ() || this.a.hasEffect(MobEffects.LEVITATION)) && this.a.getControllerMove() instanceof EntitySlime.ControllerMoveSlime && this.a.canWander; // Paper - add canWander + } + + @Override + public void e() { + if (--this.c <= 0) { + this.c = 40 + this.a.getRandom().nextInt(60); +- this.b = (float) this.a.getRandom().nextInt(360); ++ // Paper start ++ SlimeChangeDirectionEvent event = new SlimeChangeDirectionEvent((Slime) this.a.getBukkitEntity(), (float) this.a.getRandom().nextInt(360)); ++ if (!this.a.canWander || !event.callEvent()) return; ++ this.b = event.getNewYaw(); ++ // Paper end + } + + ((EntitySlime.ControllerMoveSlime) this.a.getControllerMove()).a(this.b, false); +@@ -473,7 +491,15 @@ public class EntitySlime extends EntityInsentient implements IMonster { + public boolean a() { + EntityLiving entityliving = this.a.getGoalTarget(); + +- return entityliving == null ? false : (!entityliving.isAlive() ? false : (entityliving instanceof EntityHuman && ((EntityHuman) entityliving).abilities.isInvulnerable ? false : this.a.getControllerMove() instanceof EntitySlime.ControllerMoveSlime)); ++ // Paper start ++ if (entityliving == null || !entityliving.isAlive()) { ++ return false; ++ } ++ if (entityliving instanceof EntityHuman && ((EntityHuman) entityliving).abilities.isInvulnerable) { ++ return false; ++ } ++ return this.a.getControllerMove() instanceof EntitySlime.ControllerMoveSlime && this.a.canWander && new SlimeTargetLivingEntityEvent((Slime) this.a.getBukkitEntity(), (LivingEntity) entityliving.getBukkitEntity()).callEvent(); ++ // Paper end + } + + @Override +@@ -486,7 +512,15 @@ public class EntitySlime extends EntityInsentient implements IMonster { + public boolean b() { + EntityLiving entityliving = this.a.getGoalTarget(); + +- return entityliving == null ? false : (!entityliving.isAlive() ? false : (entityliving instanceof EntityHuman && ((EntityHuman) entityliving).abilities.isInvulnerable ? false : --this.b > 0)); ++ // Paper start ++ if (entityliving == null || !entityliving.isAlive()) { ++ return false; ++ } ++ if (entityliving instanceof EntityHuman && ((EntityHuman) entityliving).abilities.isInvulnerable) { ++ return false; ++ } ++ return --this.b > 0 && this.a.canWander && new SlimeTargetLivingEntityEvent((Slime) this.a.getBukkitEntity(), (LivingEntity) entityliving.getBukkitEntity()).callEvent(); ++ // Paper end + } + + @Override +@@ -494,6 +528,13 @@ public class EntitySlime extends EntityInsentient implements IMonster { + this.a.a((Entity) this.a.getGoalTarget(), 10.0F, 10.0F); + ((EntitySlime.ControllerMoveSlime) this.a.getControllerMove()).a(this.a.yaw, this.a.eL()); + } ++ ++ // Paper start - clear timer and target when goal resets ++ public void d() { ++ this.b = 0; ++ this.a.setGoalTarget(null); ++ } ++ // Paper end + } + + static class ControllerMoveSlime extends ControllerMove { +@@ -552,4 +593,15 @@ public class EntitySlime extends EntityInsentient implements IMonster { + } + } + } ++ ++ // Paper start ++ private boolean canWander = true; ++ public boolean canWander() { ++ return canWander; ++ } ++ ++ public void setWander(boolean canWander) { ++ this.canWander = canWander; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +index aa0ac8d8493dc79dda3fed2ff4d80c5b7a7e3df6..cf5c6030105e56813f526e710e5db0c59d88c99e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +@@ -35,4 +35,14 @@ public class CraftSlime extends CraftMob implements Slime { + public EntityType getType() { + return EntityType.SLIME; + } ++ ++ // Paper start ++ public boolean canWander() { ++ return getHandle().canWander(); ++ } ++ ++ public void setWander(boolean canWander) { ++ getHandle().setWander(canWander); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0272-Configurable-speed-for-water-flowing-over-lava.patch b/patches/server-unmapped/0001/0272-Configurable-speed-for-water-flowing-over-lava.patch new file mode 100644 index 0000000000..55ef5bbfa1 --- /dev/null +++ b/patches/server-unmapped/0001/0272-Configurable-speed-for-water-flowing-over-lava.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Wed, 8 Aug 2018 16:33:21 -0600 +Subject: [PATCH] Configurable speed for water flowing over lava + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 5ab0e7183e48134b7a0f736462516b1a8a333b04..f280dbff4a09bc611a9ca565c6d697d08801f53b 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -390,4 +390,10 @@ public class PaperWorldConfig { + this.armorStandTick = this.getBoolean("armor-stands-tick", this.armorStandTick); + log("ArmorStand ticking is " + (this.armorStandTick ? "enabled" : "disabled") + " by default"); + } ++ ++ public int waterOverLavaFlowSpeed; ++ private void waterOverLavaFlowSpeed() { ++ waterOverLavaFlowSpeed = getInt("water-over-lava-flow-speed", 5); ++ log("Water over lava flow speed: " + waterOverLavaFlowSpeed); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/BlockFluids.java b/src/main/java/net/minecraft/world/level/block/BlockFluids.java +index 0654c77ab059dea2ad06cb16d07950e153d3f15d..0ed8d938b8fafdb03e01a00a201ba3f8597ac6e9 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockFluids.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockFluids.java +@@ -23,6 +23,7 @@ import net.minecraft.world.level.material.Fluid; + import net.minecraft.world.level.material.FluidType; + import net.minecraft.world.level.material.FluidTypeFlowing; + import net.minecraft.world.level.material.FluidTypes; ++import net.minecraft.world.level.material.Material; + import net.minecraft.world.level.pathfinder.PathMode; + import net.minecraft.world.level.storage.loot.LootTableInfo; + import net.minecraft.world.phys.shapes.VoxelShape; +@@ -100,11 +101,28 @@ public class BlockFluids extends Block implements IFluidSource { + @Override + public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { + if (this.a(world, blockposition, iblockdata)) { +- world.getFluidTickList().a(blockposition, iblockdata.getFluid().getType(), this.b.a((IWorldReader) world)); ++ world.getFluidTickList().a(blockposition, iblockdata.getFluid().getType(), this.getFlowSpeed(world, blockposition)); // Paper + } + + } + ++ // Paper start - Get flow speed. Throttle if its water and flowing adjacent to lava ++ public int getFlowSpeed(World world, BlockPosition blockposition) { ++ if (this.material == Material.WATER) { ++ if ( ++ world.getMaterialIfLoaded(blockposition.north(1)) == Material.LAVA || ++ world.getMaterialIfLoaded(blockposition.south(1)) == Material.LAVA || ++ world.getMaterialIfLoaded(blockposition.west(1)) == Material.LAVA || ++ world.getMaterialIfLoaded(blockposition.east(1)) == Material.LAVA ++ ) { ++ return world.paperConfig.waterOverLavaFlowSpeed; ++ } ++ } ++ return this.b.a(world); ++ } ++ // Paper end ++ ++ + @Override + public IBlockData updateState(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { + if (iblockdata.getFluid().isSource() || iblockdata1.getFluid().isSource()) { +@@ -117,7 +135,7 @@ public class BlockFluids extends Block implements IFluidSource { + @Override + public void doPhysics(IBlockData iblockdata, World world, BlockPosition blockposition, Block block, BlockPosition blockposition1, boolean flag) { + if (this.a(world, blockposition, iblockdata)) { +- world.getFluidTickList().a(blockposition, iblockdata.getFluid().getType(), this.b.a((IWorldReader) world)); ++ world.getFluidTickList().a(blockposition, iblockdata.getFluid().getType(), this.getFlowSpeed(world, blockposition)); // Paper + } + + } diff --git a/patches/server-unmapped/0001/0273-Optimize-CraftBlockData-Creation.patch b/patches/server-unmapped/0001/0273-Optimize-CraftBlockData-Creation.patch new file mode 100644 index 0000000000..727414b274 --- /dev/null +++ b/patches/server-unmapped/0001/0273-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/BlockBase.java b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +index 10f4015b8c36e4e27cf7d0745ba70b8a9e60aff3..57857cc33603cf278de424b540a3d4a5943584c9 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +@@ -375,6 +375,14 @@ public abstract class BlockBase { + this.o = blockbase_info.t; + this.p = blockbase_info.u; + } ++ // Paper start - 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(getBlockData()); ++ return (org.bukkit.craftbukkit.block.data.CraftBlockData) cachedCraftBlockData.clone(); ++ } ++ // Paper end + + public void a() { + if (!this.getBlock().o()) { +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 76fd1fea386d0e65c63c529dba772f01d9888407..ed88da727feddc319a650fb35710d16727f6dbd7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -509,7 +509,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.REGISTRY_ID.iterator().forEachRemaining(IBlockData::createCraftBlockData); ++ } + public static CraftBlockData fromData(IBlockData data) { ++ return data.createCraftBlockData(); ++ } ++ ++ public static CraftBlockData createData(IBlockData data) { ++ // Paper end + return MAP.getOrDefault(data.getBlock().getClass(), CraftBlockData::new).apply(data); + } + diff --git a/patches/server-unmapped/0001/0274-Optimize-RegistryMaterials.patch b/patches/server-unmapped/0001/0274-Optimize-RegistryMaterials.patch new file mode 100644 index 0000000000..a37df72fa3 --- /dev/null +++ b/patches/server-unmapped/0001/0274-Optimize-RegistryMaterials.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 26 Aug 2018 20:49:50 -0400 +Subject: [PATCH] Optimize RegistryMaterials + +Use larger initial sizes to increase bucket capacity on the BiMap + +BiMap.get was seen to be using a good bit of CPU time. + +diff --git a/src/main/java/net/minecraft/core/RegistryMaterials.java b/src/main/java/net/minecraft/core/RegistryMaterials.java +index f3f6ed83d509d228944d15fc2b2b4cb85b05e366..18f78a5cb03dd9c8349c28b99d013752e32c9167 100644 +--- a/src/main/java/net/minecraft/core/RegistryMaterials.java ++++ b/src/main/java/net/minecraft/core/RegistryMaterials.java +@@ -30,6 +30,7 @@ import net.minecraft.SystemUtils; + import net.minecraft.resources.MinecraftKey; + import net.minecraft.resources.RegistryDataPackCodec; + import net.minecraft.resources.ResourceKey; ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; // Paper + import org.apache.commons.lang3.Validate; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -38,7 +39,7 @@ public class RegistryMaterials extends IRegistryWritable { + + protected static final Logger LOGGER = LogManager.getLogger(); + private final ObjectList bf = new ObjectArrayList(256); +- private final Object2IntMap bg = new Object2IntOpenCustomHashMap(SystemUtils.k()); ++ private final Reference2IntOpenHashMap bg = new Reference2IntOpenHashMap(2048);// Paper - use bigger expected size to reduce collisions and direct intent for FastUtil to be identity map + private final BiMap bh; + private final BiMap, T> bi; + private final Map bj; +@@ -49,9 +50,9 @@ public class RegistryMaterials extends IRegistryWritable { + public RegistryMaterials(ResourceKey> resourcekey, Lifecycle lifecycle) { + super(resourcekey, lifecycle); + this.bg.defaultReturnValue(-1); +- this.bh = HashBiMap.create(); +- this.bi = HashBiMap.create(); +- this.bj = Maps.newIdentityHashMap(); ++ this.bh = HashBiMap.create(2048); // Paper - use bigger expected size to reduce collisions ++ this.bi = HashBiMap.create(2048); // Paper - use bigger expected size to reduce collisions ++ this.bj = new java.util.IdentityHashMap<>(2048); // Paper - use bigger expected size to reduce collisions + this.bk = lifecycle; + } + +@@ -195,7 +196,7 @@ public class RegistryMaterials extends IRegistryWritable { + this.b = collection.toArray(new Object[collection.size()]); + } + +- return SystemUtils.a(this.b, random); ++ return (T) SystemUtils.a(this.b, random); // Paper - Decompile fix + } + + public static Codec> a(ResourceKey> resourcekey, Lifecycle lifecycle, Codec codec) { +@@ -215,7 +216,7 @@ public class RegistryMaterials extends IRegistryWritable { + Iterator iterator = registrymaterials.iterator(); + + while (iterator.hasNext()) { +- T t0 = iterator.next(); ++ T t0 = (T) iterator.next(); // Paper - Decompile fix + + builder.add(new RegistryMaterials.a<>((ResourceKey) registrymaterials.c(t0).get(), registrymaterials.a(t0), t0)); + } diff --git a/patches/server-unmapped/0001/0275-Add-PhantomPreSpawnEvent.patch b/patches/server-unmapped/0001/0275-Add-PhantomPreSpawnEvent.patch new file mode 100644 index 0000000000..dca07a67d6 --- /dev/null +++ b/patches/server-unmapped/0001/0275-Add-PhantomPreSpawnEvent.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 25 Aug 2018 19:56:51 -0500 +Subject: [PATCH] Add PhantomPreSpawnEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java b/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java +index 6053894c5250e9a1a0c4aa2d681127dfd652b34f..6c498d4345df35a411d155799ac56e47c9c48114 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java +@@ -161,6 +161,11 @@ public class EntityPhantom extends EntityFlying implements IMonster { + } + + this.setSize(nbttagcompound.getInt("Size")); ++ // Paper start ++ if (nbttagcompound.hasUUID("Paper.SpawningEntity")) { ++ this.spawningEntity = nbttagcompound.getUUID("Paper.SpawningEntity"); ++ } ++ // Paper end + } + + @Override +@@ -170,6 +175,11 @@ public class EntityPhantom extends EntityFlying implements IMonster { + nbttagcompound.setInt("AY", this.d.getY()); + nbttagcompound.setInt("AZ", this.d.getZ()); + nbttagcompound.setInt("Size", this.getSize()); ++ // Paper start ++ if (this.spawningEntity != null) { ++ nbttagcompound.setUUID("Paper.SpawningEntity", this.spawningEntity); ++ } ++ // Paper end + } + + @Override +@@ -216,6 +226,15 @@ public class EntityPhantom extends EntityFlying implements IMonster { + return entitysize.a(f); + } + ++ // Paper start ++ java.util.UUID spawningEntity; ++ ++ public java.util.UUID getSpawningEntity() { ++ return spawningEntity; ++ } ++ public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } ++ // Paper end ++ + class b extends PathfinderGoal { + + private final PathfinderTargetCondition b; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPhantom.java b/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPhantom.java +index cfc32acee1e456a0fda12a5faa4035e29d0c3d5e..96a5a6569387a25b15a06aaab3bd9d033547e875 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPhantom.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPhantom.java +@@ -4,6 +4,7 @@ import java.util.Iterator; + import java.util.Random; + import net.minecraft.core.BlockPosition; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.WorldServer; + import net.minecraft.stats.ServerStatisticManager; +@@ -73,8 +74,17 @@ public class MobSpawnerPhantom implements MobSpawner { + int k = 1 + random.nextInt(difficultydamagescaler.a().a() + 1); + + for (int l = 0; l < k; ++l) { ++ // Paper start ++ com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent event = new com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent(MCUtil.toLocation(worldserver, blockposition1), ((EntityPlayer) entityhuman).getBukkitEntity(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ break; ++ } ++ continue; ++ } ++ // Paper end + EntityPhantom entityphantom = (EntityPhantom) EntityTypes.PHANTOM.a((World) worldserver); +- ++ entityphantom.setSpawningEntity(entityhuman.getUniqueID()); // Paper + entityphantom.setPositionRotation(blockposition1, 0.0F, 0.0F); + groupdataentity = entityphantom.prepare(worldserver, difficultydamagescaler, EnumMobSpawn.NATURAL, groupdataentity, (NBTTagCompound) null); + worldserver.addAllEntities(entityphantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +index 6ec7cf434b6586342da3f351466f5c7d72df290d..0cea1d8e23da3a79ef06e43752665a5401b01b4b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +@@ -35,4 +35,10 @@ public class CraftPhantom extends CraftFlying implements Phantom { + public EntityType getType() { + return EntityType.PHANTOM; + } ++ ++ // Paper start ++ public java.util.UUID getSpawningEntity() { ++ return getHandle().getSpawningEntity(); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0276-Add-More-Creeper-API.patch b/patches/server-unmapped/0001/0276-Add-More-Creeper-API.patch new file mode 100644 index 0000000000..faced7350e --- /dev/null +++ b/patches/server-unmapped/0001/0276-Add-More-Creeper-API.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 24 Aug 2018 11:50:26 -0500 +Subject: [PATCH] Add More Creeper API + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java b/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java +index cbb973e077e04e5221bcc837f434b7093bdbcc2a..b47f71ca1f1c8bbd1a521836d9cb5d676a33ec76 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java +@@ -290,7 +290,18 @@ public class EntityCreeper extends EntityMonster { + } + + public void ignite() { +- this.datawatcher.set(EntityCreeper.d, true); ++ // Paper start ++ setIgnited(true); ++ } ++ ++ public void setIgnited(boolean ignited) { ++ if (isIgnited() != ignited) { ++ com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited); ++ if (event.callEvent()) { ++ this.datawatcher.set(EntityCreeper.d, event.isIgnited()); ++ } ++ } ++ // Paper end + } + + public boolean canCauseHeadDrop() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java +index 167b8f0c742be07ee0c5d698e04d6e29addda70c..629518d4bb314a1d46e32397b6fb7b90bce94e83 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java +@@ -101,4 +101,14 @@ public class CraftCreeper extends CraftMonster implements Creeper { + public EntityType getType() { + return EntityType.CREEPER; + } ++ ++ // Paper start ++ public void setIgnited(boolean ignited) { ++ getHandle().setIgnited(ignited); ++ } ++ ++ public boolean isIgnited() { ++ return getHandle().isIgnited(); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0277-Inventory-removeItemAnySlot.patch b/patches/server-unmapped/0001/0277-Inventory-removeItemAnySlot.patch new file mode 100644 index 0000000000..8d4a86803e --- /dev/null +++ b/patches/server-unmapped/0001/0277-Inventory-removeItemAnySlot.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 28 Aug 2018 23:04:15 -0400 +Subject: [PATCH] Inventory#removeItemAnySlot + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +index 4211bdfb213db0781107c67f1522e9689aa7ecfe..45634fded9916dca35a246921efb87964c860339 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +@@ -223,10 +223,16 @@ public class CraftInventory implements Inventory { + } + + private int first(ItemStack item, boolean withAmount) { ++ // Paper start ++ return first(item, withAmount, getStorageContents()); ++ } ++ ++ private int first(ItemStack item, boolean withAmount, ItemStack[] inventory) { ++ // Paper end + if (item == null) { + return -1; + } +- ItemStack[] inventory = getStorageContents(); ++ //ItemStack[] inventory = getStorageContents(); // Paper - let param deal + for (int i = 0; i < inventory.length; i++) { + if (inventory[i] == null) continue; + +@@ -349,6 +355,17 @@ public class CraftInventory implements Inventory { + + @Override + public HashMap removeItem(ItemStack... items) { ++ // Paper start ++ return removeItem(false, items); ++ } ++ ++ @Override ++ public HashMap removeItemAnySlot(ItemStack... items) { ++ return removeItem(true, items); ++ } ++ ++ private HashMap removeItem(boolean searchEntire, ItemStack... items) { ++ // Paper end + Validate.notNull(items, "Items cannot be null"); + HashMap leftover = new HashMap(); + +@@ -359,7 +376,10 @@ public class CraftInventory implements Inventory { + int toDelete = item.getAmount(); + + while (true) { +- int first = first(item, false); ++ // Paper start - Allow searching entire contents ++ ItemStack[] toSearch = searchEntire ? getContents() : getStorageContents(); ++ int first = first(item, false, toSearch); ++ // Paper end + + // Drat! we don't have this type in the inventory + if (first == -1) { diff --git a/patches/server-unmapped/0001/0278-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch b/patches/server-unmapped/0001/0278-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch new file mode 100644 index 0000000000..1393753e32 --- /dev/null +++ b/patches/server-unmapped/0001/0278-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 716974f9f455a8b35a2bee0ef17e795676f44105..796fd718f3fe57618aedc9f5a41aac64422e3e34 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -508,7 +508,7 @@ public class CraftWorld implements World { + @Override + public boolean loadChunk(int x, int z, boolean generate) { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot +- IChunkAccess chunk = world.getChunkProvider().getChunkAt(x, z, generate ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); ++ IChunkAccess chunk = world.getChunkProvider().getChunkAt(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 ProtoChunkExtension) { diff --git a/patches/server-unmapped/0001/0279-Add-ray-tracing-methods-to-LivingEntity.patch b/patches/server-unmapped/0001/0279-Add-ray-tracing-methods-to-LivingEntity.patch new file mode 100644 index 0000000000..a8ed3ec817 --- /dev/null +++ b/patches/server-unmapped/0001/0279-Add-ray-tracing-methods-to-LivingEntity.patch @@ -0,0 +1,99 @@ +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/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 2b10ae84ee8e9f63382d732e8c051fc47f0c5d9f..a64b2953c43138491cdab3e3e24e2e7ed969e171 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -3585,6 +3585,23 @@ public abstract class EntityLiving extends Entity { + this.broadcastItemBreak(enumhand == EnumHand.MAIN_HAND ? EnumItemSlot.MAINHAND : EnumItemSlot.OFFHAND); + } + // Paper start ++ public MovingObjectPosition getRayTrace(int maxDistance) { ++ return getRayTrace(maxDistance, RayTrace.FluidCollisionOption.NONE); ++ } ++ ++ public MovingObjectPosition getRayTrace(int maxDistance, RayTrace.FluidCollisionOption fluidCollisionOption) { ++ if (maxDistance < 1 || maxDistance > 120) { ++ throw new IllegalArgumentException("maxDistance must be between 1-120"); ++ } ++ ++ Vec3D start = new Vec3D(locX(), locY() + getHeadHeight(), locZ()); ++ org.bukkit.util.Vector dir = getBukkitEntity().getLocation().getDirection().multiply(maxDistance); ++ Vec3D end = new Vec3D(start.x + dir.getX(), start.y + dir.getY(), start.z + dir.getZ()); ++ RayTrace raytrace = new RayTrace(start, end, RayTrace.BlockCollisionOption.OUTLINE, fluidCollisionOption, this); ++ ++ return world.rayTrace(raytrace); ++ } ++ + public int shieldBlockingDelay = world.paperConfig.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 bd24b9865f37c34ffd63cd411ddc84abe5ab30d0..c692626b747008a5418ecabf550fc67e3b676f5b 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.block.TargetBlockInfo; + import com.google.common.base.Preconditions; + import com.google.common.collect.Sets; + import java.util.ArrayList; +@@ -8,6 +9,7 @@ import java.util.Iterator; + import java.util.List; + import java.util.Set; + import java.util.UUID; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.world.EnumHand; + import net.minecraft.world.damagesource.DamageSource; +@@ -40,6 +42,8 @@ import net.minecraft.world.entity.projectile.EntityThrownExpBottle; + import net.minecraft.world.entity.projectile.EntityThrownTrident; + import net.minecraft.world.entity.projectile.EntityTippedArrow; + import net.minecraft.world.entity.projectile.EntityWitherSkull; ++import net.minecraft.world.phys.MovingObjectPosition; ++import net.minecraft.world.phys.MovingObjectPositionBlock; + import org.apache.commons.lang.Validate; + import org.bukkit.FluidCollisionMode; + import org.bukkit.Location; +@@ -49,6 +53,7 @@ import org.bukkit.attribute.AttributeInstance; + import org.bukkit.block.Block; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.craftbukkit.entity.memory.CraftMemoryKey; + import org.bukkit.craftbukkit.entity.memory.CraftMemoryMapper; + import org.bukkit.craftbukkit.inventory.CraftEntityEquipment; +@@ -202,6 +207,28 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return blocks.get(0); + } + ++ // Paper start ++ @Override ++ public Block getTargetBlock(int maxDistance, TargetBlockInfo.FluidMode fluidMode) { ++ MovingObjectPosition rayTrace = getHandle().getRayTrace(maxDistance, MCUtil.getNMSFluidCollisionOption(fluidMode)); ++ return !(rayTrace instanceof MovingObjectPositionBlock) ? null : CraftBlock.at(getHandle().world, ((MovingObjectPositionBlock)rayTrace).getBlockPosition()); ++ } ++ ++ @Override ++ public org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance, TargetBlockInfo.FluidMode fluidMode) { ++ MovingObjectPosition rayTrace = getHandle().getRayTrace(maxDistance, MCUtil.getNMSFluidCollisionOption(fluidMode)); ++ return !(rayTrace instanceof MovingObjectPositionBlock) ? null : MCUtil.toBukkitBlockFace(((MovingObjectPositionBlock)rayTrace).getDirection()); ++ } ++ ++ @Override ++ public TargetBlockInfo getTargetBlockInfo(int maxDistance, TargetBlockInfo.FluidMode fluidMode) { ++ MovingObjectPosition rayTrace = getHandle().getRayTrace(maxDistance, MCUtil.getNMSFluidCollisionOption(fluidMode)); ++ return !(rayTrace instanceof MovingObjectPositionBlock) ? null : ++ new TargetBlockInfo(CraftBlock.at(getHandle().world, ((MovingObjectPositionBlock)rayTrace).getBlockPosition()), ++ MCUtil.toBukkitBlockFace(((MovingObjectPositionBlock)rayTrace).getDirection())); ++ } ++ // Paper end ++ + @Override + public List getLastTwoTargetBlocks(Set transparent, int maxDistance) { + return getLineOfSight(transparent, maxDistance, 2); diff --git a/patches/server-unmapped/0001/0280-Expose-attack-cooldown-methods-for-Player.patch b/patches/server-unmapped/0001/0280-Expose-attack-cooldown-methods-for-Player.patch new file mode 100644 index 0000000000..11610da177 --- /dev/null +++ b/patches/server-unmapped/0001/0280-Expose-attack-cooldown-methods-for-Player.patch @@ -0,0 +1,41 @@ +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/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index b6effe1037f3ae59e6faa5f5d039b6ad54bca5d4..87374174dcbf9e7ee448a1cdd9a3528557c3a2ea 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -2103,6 +2103,7 @@ public abstract class EntityHuman extends EntityLiving { + this.datawatcher.set(EntityHuman.bl, nbttagcompound); + } + ++ public float getCooldownPeriod() { return this.eR(); } // Paper - OBFHELPER + public float eR() { + return (float) (1.0D / this.b(GenericAttributes.ATTACK_SPEED) * 20.0D); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 32228b4eddaadabbae46ebbc5eb3404acf73fb29..9d3e01f7ad743dbe60685e9b111308ed06a0b4b7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2186,6 +2186,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + connection.sendPacket(new net.minecraft.network.protocol.game.PacketPlayOutOpenBook(net.minecraft.world.EnumHand.MAIN_HAND)); + connection.sendPacket(new net.minecraft.network.protocol.game.PacketPlayOutSetSlot(0, slot, inventory.getItemInHand())); + } ++ ++ public float getCooldownPeriod() { ++ return getHandle().getCooldownPeriod(); ++ } ++ ++ public float getCooledAttackStrength(float adjustTicks) { ++ return getHandle().getAttackCooldown(adjustTicks); ++ } ++ ++ public void resetCooldown() { ++ getHandle().resetAttackCooldown(); ++ } + // Paper end + + // Spigot start diff --git a/patches/server-unmapped/0001/0281-Improve-death-events.patch b/patches/server-unmapped/0001/0281-Improve-death-events.patch new file mode 100644 index 0000000000..7ba06b87b7 --- /dev/null +++ b/patches/server-unmapped/0001/0281-Improve-death-events.patch @@ -0,0 +1,425 @@ +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. + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 7b07d17f8ae425418ff3cafdfd30d72a5bc0fe16..1271de75743356090050763ff2fb67d28b48cb23 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -213,6 +213,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public int ping; + public boolean viewingCredits; + private int containerUpdateDelay; // Paper ++ // Paper start - cancellable death event ++ public boolean queueHealthUpdatePacket = false; ++ public net.minecraft.network.protocol.game.PacketPlayOutUpdateHealth queuedHealthUpdatePacket; ++ // Paper end + + // CraftBukkit start + public String displayName; +@@ -716,6 +720,15 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + IChatBaseComponent defaultMessage = this.getCombatTracker().getDeathMessage(); + + org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, PaperAdventure.asAdventure(defaultMessage), defaultMessage.getString(), 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; ++ } ++ // Paper end + + // SPIGOT-943 - only call if they have an inventory open + if (this.activeContainer != this.defaultContainer) { +@@ -862,8 +875,17 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + } + } +- +- return super.damageEntity(damagesource, f); ++ // Paper start - cancellable death events ++ //return super.damageEntity(damagesource, f); ++ this.queueHealthUpdatePacket = true; ++ boolean damaged = super.damageEntity(damagesource, f); ++ this.queueHealthUpdatePacket = false; ++ if (this.queuedHealthUpdatePacket != null) { ++ this.playerConnection.sendPacket(this.queuedHealthUpdatePacket); ++ this.queuedHealthUpdatePacket = null; ++ } ++ return damaged; ++ // Paper end + } + } + } +diff --git a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java +index f6f79ed9c38206cc6a4feb5504e854a476868aec..7d2b947b3c2b255c01241f2c4a6d7377a0a7c671 100644 +--- a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java ++++ b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java +@@ -203,6 +203,7 @@ public class CombatTracker { + this.h = null; + } + ++ public final void reset() { this.g(); } // Paper - OBFHELPER + public void g() { + int i = this.f ? 300 : 100; + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 046b191e771ed9be337e095214a67febd768e5f6..b6b4eb9ac883cfdfab5f114767fb5cfb29445730 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1538,6 +1538,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + // CraftBukkit end + ++ public final void runKillTrigger(Entity entity, int kills, DamageSource damageSource) { this.a(entity, kills, damageSource); } // Paper - OBFHELPER + public void a(Entity entity, int i, DamageSource damagesource) { + if (entity instanceof EntityPlayer) { + CriterionTriggers.c.a((EntityPlayer) entity, this, damagesource); +@@ -2437,6 +2438,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + this.fallDistance = 0.0F; + } + ++ public final void onKill(WorldServer worldserver, EntityLiving entityLiving) { this.a(worldserver, entityLiving); } // Paper - OBFHELPER + public void a(WorldServer worldserver, EntityLiving entityliving) {} + + protected void l(double d0, double d1, double d2) { +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index a64b2953c43138491cdab3e3e24e2e7ed969e171..b3c2976a48c2349e5c22d58dd1ac64a02cd969d5 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -189,7 +189,7 @@ public abstract class EntityLiving extends Entity { + protected float aL; + protected float aM; + protected float aN; +- protected int aO; ++ protected int aO;protected int getKillCount() { return this.aO; } // Paper - OBFHELPER + public float lastDamage; + protected boolean jumping; + public float aR; +@@ -233,6 +233,7 @@ public abstract class EntityLiving extends Entity { + public Set collidableExemptions = new HashSet<>(); + public boolean canPickUpLoot; + 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() { +@@ -1348,13 +1349,17 @@ public abstract class EntityLiving extends Entity { + if (knockbackCancelled) this.world.broadcastEntityEffect(this, (byte) 2); // Paper - Disable explosion knockback + if (this.dl()) { + if (!this.f(damagesource)) { +- SoundEffect soundeffect = this.getSoundDeath(); ++ // Paper start - moved into CraftEventFactory event caller for cancellable death event ++ //SoundEffect soundeffect = this.getSoundDeath(); + +- if (flag1 && soundeffect != null) { +- this.playSound(soundeffect, this.getSoundVolume(), this.dH()); +- } ++// if (flag1 && soundeffect != null) { ++// this.playSound(soundeffect, this.getSoundVolume(), this.dH()); ++// } ++ this.silentDeath = !flag1; // mark entity as dying silently ++ // Paper end + + this.die(damagesource); ++ this.silentDeath = false; // Paper - cancellable death event - reset to default + } + } else if (flag1) { + this.c(damagesource); +@@ -1493,6 +1498,7 @@ public abstract class EntityLiving extends Entity { + Entity entity = damagesource.getEntity(); + EntityLiving entityliving = this.getKillingEntity(); + ++ /* // Paper - move down to make death event cancellable - this is the runKillTrigger below + if (this.aO >= 0 && entityliving != null) { + entityliving.a(this, this.aO, damagesource); + } +@@ -1500,20 +1506,40 @@ public abstract class EntityLiving extends Entity { + if (this.isSleeping()) { + this.entityWakeup(); + } ++ */ // Paper + + this.killed = true; +- this.getCombatTracker().g(); ++ // this.getCombatTracker().g(); // Paper - moved into if below as .reset() + if (this.world instanceof WorldServer) { + if (entity != null) { +- entity.a((WorldServer) this.world, this); ++ // entity.a((WorldServer) this.world, this); // Paper - move below into if for onKill + } + +- this.d(damagesource); ++ // Paper start ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.d(damagesource); ++ if (deathEvent == null || !deathEvent.isCancelled()) { ++ if (this.getKillCount() >= 0 && entityliving != null) { ++ entityliving.runKillTrigger(this, this.getKillCount(), damagesource); ++ } ++ if (this.isSleeping()) { ++ this.entityWakeup(); ++ } ++ this.getCombatTracker().reset(); ++ if (entity != null) { ++ entity.onKill((WorldServer) this.world, this); ++ } ++ } else { ++ this.killed = false; ++ this.setHealth((float) deathEvent.getReviveHealth()); ++ } ++ // Paper end + this.f(entityliving); + } + ++ if (this.killed) { // Paper + this.world.broadcastEntityEffect(this, (byte) 3); + this.setPose(EntityPose.DYING); ++ } // Paper + } + } + +@@ -1521,7 +1547,7 @@ public abstract class EntityLiving extends Entity { + if (!this.world.isClientSide) { + boolean flag = false; + +- if (entityliving instanceof EntityWither) { ++ if (this.killed && entityliving instanceof EntityWither) { // Paper + if (this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { + BlockPosition blockposition = this.getChunkCoordinates(); + IBlockData iblockdata = Blocks.WITHER_ROSE.getBlockData(); +@@ -1549,7 +1575,8 @@ public abstract class EntityLiving extends Entity { + } + } + +- protected void d(DamageSource damagesource) { ++ protected org.bukkit.event.entity.EntityDeathEvent processDeath(DamageSource damagesource) { return d(damagesource); } // Paper - OBFHELPER ++ protected org.bukkit.event.entity.EntityDeathEvent d(DamageSource damagesource) { // Paper + Entity entity = damagesource.getEntity(); + int i; + +@@ -1567,15 +1594,18 @@ public abstract class EntityLiving extends Entity { + this.dropDeathLoot(damagesource, i, flag); + } + // 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 dropInventory() {} ++ 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() { +@@ -1660,6 +1690,7 @@ public abstract class EntityLiving extends Entity { + return SoundEffects.ENTITY_GENERIC_HURT; + } + ++ public final SoundEffect getDeathSoundEffect() { return this.getSoundDeath(); } // Paper - OBFHELPER + @Nullable + protected SoundEffect getSoundDeath() { + return SoundEffects.ENTITY_GENERIC_DEATH; +@@ -2196,10 +2227,12 @@ public abstract class EntityLiving extends Entity { + + } + ++ public final float getDeathSoundVolume() { return this.getSoundVolume(); } // Paper - OBFHELPER + protected float getSoundVolume() { + return 1.0F; + } + ++ public float getSoundPitch() { return dH();} // Paper - OBFHELPER + protected float dH() { + return this.isBaby() ? (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.5F : (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F; + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityFox.java b/src/main/java/net/minecraft/world/entity/animal/EntityFox.java +index 459b7727e946679989477f4a7e99c5ca47ac0b30..a3b714a9d63c6bb33a2731fb9293c9d155754b17 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityFox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityFox.java +@@ -647,15 +647,25 @@ public class EntityFox extends EntityAnimal { + } + + @Override +- protected void d(DamageSource damagesource) { +- ItemStack itemstack = this.getEquipment(EnumItemSlot.MAINHAND); ++ protected org.bukkit.event.entity.EntityDeathEvent d(DamageSource damagesource) { // Paper ++ ItemStack itemstack = this.getEquipment(EnumItemSlot.MAINHAND).cloneItemStack(); // Paper ++ ++ // Paper start - Cancellable death event ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = super.d(damagesource); ++ ++ // Below is code to drop ++ ++ if (deathEvent == null || deathEvent.isCancelled()) { ++ return deathEvent; ++ } ++ // Paper end + + if (!itemstack.isEmpty()) { + this.a(itemstack); + this.setSlot(EnumItemSlot.MAINHAND, ItemStack.b); + } + +- super.d(damagesource); ++ return deathEvent; // Paper + } + + public static boolean a(EntityFox entityfox, EntityLiving entityliving) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseChestedAbstract.java b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseChestedAbstract.java +index aa12a0c9f30cd2b8a6de75ff9822843da808ae64..3daa1780a332128bd472fa80039112f3ca9bc4e9 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseChestedAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseChestedAbstract.java +@@ -68,11 +68,19 @@ public abstract class EntityHorseChestedAbstract extends EntityHorseAbstract { + this.a((IMaterial) Blocks.CHEST); + } + +- this.setCarryingChest(false); ++ //this.setCarryingChest(false); // Paper - moved to post death logic + } + + } + ++ // Paper start ++ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) { ++ if (this.isCarryingChest() && (event == null || !event.isCancelled())) { ++ this.setCarryingChest(false); ++ } ++ } ++ // Paper end ++ + @Override + public void saveData(NBTTagCompound nbttagcompound) { + super.saveData(nbttagcompound); +diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +index 8d35240405d7f7245f3c7b0b611973d58fa4384f..69361caebf0d3caa5195b519a16691705ac5e16a 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +@@ -746,7 +746,8 @@ public class EntityArmorStand extends EntityLiving { + + @Override + public void killEntity() { +- org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, drops); // CraftBukkit - call event ++ org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, drops); // CraftBukkit - call event // Paper - make cancellable ++ if (event.isCancelled()) return; // Paper - make cancellable + this.die(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 9d3e01f7ad743dbe60685e9b111308ed06a0b4b7..2334a9a95ab0e2395744343a5a1e3d26c88b7dc3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1841,7 +1841,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void sendHealthUpdate() { +- getHandle().playerConnection.sendPacket(new PacketPlayOutUpdateHealth(getScaledHealth(), getHandle().getFoodData().getFoodLevel(), getHandle().getFoodData().getSaturationLevel())); ++ // Paper start - cancellable death event ++ //getHandle().playerConnection.sendPacket(new PacketPlayOutUpdateHealth(getScaledHealth(), getHandle().getFoodData().getFoodLevel(), getHandle().getFoodData().getSaturationLevel())); ++ PacketPlayOutUpdateHealth packet = new PacketPlayOutUpdateHealth(getScaledHealth(), getHandle().getFoodData().getFoodLevel(), getHandle().getFoodData().getSaturationLevel()); ++ if (this.getHandle().queueHealthUpdatePacket) { ++ this.getHandle().queuedHealthUpdatePacket = packet; ++ } else { ++ this.getHandle().playerConnection.sendPacket(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 d65c521b6028d61eed8cd1c63f8f9e72f2aa0e3b..8ad6540fc4af95409d1c9169e3b0ed78310eeac5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -18,6 +18,8 @@ import net.minecraft.network.protocol.game.PacketPlayInCloseWindow; + import net.minecraft.resources.MinecraftKey; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.WorldServer; ++import net.minecraft.sounds.SoundCategory; ++import net.minecraft.sounds.SoundEffect; + import net.minecraft.util.Unit; + import net.minecraft.world.EnumHand; + import net.minecraft.world.IInventory; +@@ -784,9 +786,16 @@ public class CraftEventFactory { + public static EntityDeathEvent callEntityDeathEvent(EntityLiving 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()) { +@@ -802,8 +811,15 @@ public class CraftEventFactory { + CraftPlayer entity = victim.getBukkitEntity(); + PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage, stringDeathMessage); // Paper - Adventure + event.setKeepInventory(keepInventory); ++ 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(); +@@ -820,6 +836,31 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start - helper methods for making death event cancellable ++ // Add information to death event ++ private static void populateFields(EntityLiving victim, EntityDeathEvent event) { ++ event.setReviveHealth(event.getEntity().getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue()); ++ event.setShouldPlayDeathSound(!victim.silentDeath && !victim.isSilent()); ++ SoundEffect soundEffect = victim.getDeathSoundEffect(); ++ event.setDeathSound(soundEffect != null ? org.bukkit.craftbukkit.CraftSound.getBukkit(soundEffect) : null); ++ event.setDeathSoundCategory(org.bukkit.SoundCategory.valueOf(victim.getSoundCategory().name())); ++ event.setDeathSoundVolume(victim.getDeathSoundVolume()); ++ event.setDeathSoundPitch(victim.getSoundPitch()); ++ } ++ ++ // Play death sound manually ++ private static void playDeathSound(EntityLiving victim, EntityDeathEvent event) { ++ if (event.shouldPlayDeathSound() && event.getDeathSound() != null && event.getDeathSoundCategory() != null) { ++ EntityHuman source = victim instanceof EntityHuman ? (EntityHuman) victim : null; ++ double x = event.getEntity().getLocation().getX(); ++ double y = event.getEntity().getLocation().getY(); ++ double z = event.getEntity().getLocation().getZ(); ++ SoundEffect soundEffect = org.bukkit.craftbukkit.CraftSound.getSoundEffect(event.getDeathSound()); ++ SoundCategory soundCategory = SoundCategory.valueOf(event.getDeathSoundCategory().name()); ++ victim.world.playSound(source, x, y, z, soundEffect, soundCategory, event.getDeathSoundVolume(), event.getDeathSoundPitch()); ++ } ++ } ++ // Paper end + /** + * Server methods + */ diff --git a/patches/server-unmapped/0001/0282-Allow-chests-to-be-placed-with-NBT-data.patch b/patches/server-unmapped/0001/0282-Allow-chests-to-be-placed-with-NBT-data.patch new file mode 100644 index 0000000000..b2c6dc0b13 --- /dev/null +++ b/patches/server-unmapped/0001/0282-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 ce5d8463763dd39e1225d9dec0514b1754df5411..30db766c54db08a472caef82fdcc7cf1b7855fbf 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -298,6 +298,7 @@ public final class ItemStack { + enuminteractionresult = EnumInteractionResult.FAIL; // cancel placement + // PAIL: Remove this when MC-99075 fixed + placeEvent.getPlayer().updateInventory(); ++ world.capturedTileEntities.clear(); // Paper - clear out tile entities as chests and such will pop loot + // revert back all captured blocks + for (BlockState blockstate : blocks) { + blockstate.update(true, false); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java +index 51167d776c710decb0107bebcb35bdf43103772b..111f62d0e5b40e945793b8f504f2c035c0884a6a 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityChest.java +@@ -327,7 +327,7 @@ public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITic + // CraftBukkit start + @Override + public boolean isFilteredNBT() { +- return true; ++ return false; // Paper + } + // CraftBukkit end + } diff --git a/patches/server-unmapped/0001/0283-Mob-Pathfinding-API.patch b/patches/server-unmapped/0001/0283-Mob-Pathfinding-API.patch new file mode 100644 index 0000000000..34e0d63bf2 --- /dev/null +++ b/patches/server-unmapped/0001/0283-Mob-Pathfinding-API.patch @@ -0,0 +1,290 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 9 Sep 2018 13:30:00 -0400 +Subject: [PATCH] Mob Pathfinding API + +Implements Pathfinding API for mobs + +diff --git a/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java b/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9a3edd114c4736b1843844c6ca49da7aea7983d1 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java +@@ -0,0 +1,141 @@ ++package com.destroystokyo.paper.entity; ++ ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.level.pathfinder.PathEntity; ++import net.minecraft.world.level.pathfinder.PathPoint; ++import org.apache.commons.lang.Validate; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.entity.Mob; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import java.util.ArrayList; ++import java.util.List; ++ ++public class PaperPathfinder implements com.destroystokyo.paper.entity.Pathfinder { ++ ++ private final EntityInsentient entity; ++ ++ public PaperPathfinder(EntityInsentient entity) { ++ this.entity = entity; ++ } ++ ++ @Override ++ public Mob getEntity() { ++ return entity.getBukkitMob(); ++ } ++ ++ @Override ++ public void stopPathfinding() { ++ entity.getNavigation().stopPathfinding(); ++ } ++ ++ @Override ++ public boolean hasPath() { ++ return entity.getNavigation().getPathEntity() != null; ++ } ++ ++ @Nullable ++ @Override ++ public PathResult getCurrentPath() { ++ PathEntity path = entity.getNavigation().getPathEntity(); ++ return path != null ? new PaperPathResult(path) : null; ++ } ++ ++ @Nullable ++ @Override ++ public PathResult findPath(Location loc) { ++ Validate.notNull(loc, "Location can not be null"); ++ PathEntity path = entity.getNavigation().calculateDestination(loc.getX(), loc.getY(), loc.getZ()); ++ return path != null ? new PaperPathResult(path) : null; ++ } ++ ++ @Nullable ++ @Override ++ public PathResult findPath(LivingEntity target) { ++ Validate.notNull(target, "Target can not be null"); ++ PathEntity path = entity.getNavigation().calculateDestination(((CraftLivingEntity) target).getHandle()); ++ return path != null ? new PaperPathResult(path) : null; ++ } ++ ++ @Override ++ public boolean moveTo(@Nonnull PathResult path, double speed) { ++ Validate.notNull(path, "PathResult can not be null"); ++ PathEntity pathEntity = ((PaperPathResult) path).path; ++ return entity.getNavigation().setDestination(pathEntity, speed); ++ } ++ ++ @Override ++ public boolean canOpenDoors() { ++ return entity.getNavigation().getPathfinder().getPathfinder().shouldOpenDoors(); ++ } ++ ++ @Override ++ public void setCanOpenDoors(boolean canOpenDoors) { ++ entity.getNavigation().getPathfinder().getPathfinder().setShouldOpenDoors(canOpenDoors); ++ } ++ ++ @Override ++ public boolean canPassDoors() { ++ return entity.getNavigation().getPathfinder().getPathfinder().shouldPassDoors(); ++ } ++ ++ @Override ++ public void setCanPassDoors(boolean canPassDoors) { ++ entity.getNavigation().getPathfinder().getPathfinder().setShouldPassDoors(canPassDoors); ++ } ++ ++ @Override ++ public boolean canFloat() { ++ return entity.getNavigation().getPathfinder().getPathfinder().shouldFloat(); ++ } ++ ++ @Override ++ public void setCanFloat(boolean canFloat) { ++ entity.getNavigation().getPathfinder().getPathfinder().setShouldFloat(canFloat); ++ } ++ ++ public class PaperPathResult implements com.destroystokyo.paper.entity.PaperPathfinder.PathResult { ++ ++ private final PathEntity path; ++ PaperPathResult(PathEntity path) { ++ this.path = path; ++ } ++ ++ @Nullable ++ @Override ++ public Location getFinalPoint() { ++ PathPoint point = path.getFinalPoint(); ++ return point != null ? toLoc(point) : null; ++ } ++ ++ @Override ++ public List getPoints() { ++ List points = new ArrayList<>(); ++ for (PathPoint point : path.getPoints()) { ++ points.add(toLoc(point)); ++ } ++ return points; ++ } ++ ++ @Override ++ public int getNextPointIndex() { ++ return path.getNextIndex(); ++ } ++ ++ @Nullable ++ @Override ++ public Location getNextPoint() { ++ if (!path.hasNext()) { ++ return null; ++ } ++ return toLoc(path.getPoints().get(path.getNextIndex())); ++ } ++ } ++ ++ private Location toLoc(PathPoint point) { ++ return new Location(entity.world.getWorld(), point.getX(), point.getY(), point.getZ()); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +index d134333c736dc1ee1c722d680d7a9c22c1b265bd..06d05b511d623d0247d44989bee85b383a8fb52f 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +@@ -100,7 +100,7 @@ public abstract class NavigationAbstract { + } + + @Nullable +- public final PathEntity a(double d0, double d1, double d2, int i) { ++ public final PathEntity calculateDestination(double d0, double d1, double d2) { return a(d0, d1, d2, 0); } public final PathEntity a(double d0, double d1, double d2, int i) { // Paper - OBFHELPER + return this.a(new BlockPosition(d0, d1, d2), i); + } + +@@ -125,7 +125,7 @@ public abstract class NavigationAbstract { + } + + @Nullable +- public PathEntity a(Entity entity, int i) { ++ public final PathEntity calculateDestination(Entity entity) { return a(entity, 0); } public PathEntity a(Entity entity, int i) { + return this.a(ImmutableSet.of(entity.getChunkCoordinates()), entity, 16, true, i); // Paper + } + +@@ -190,6 +190,7 @@ public abstract class NavigationAbstract { + return pathentity != null && this.a(pathentity, d0); + } + ++ public boolean setDestination(@Nullable PathEntity pathentity, double speed) { return a(pathentity, speed); } // Paper - OBFHELPER + public boolean a(@Nullable PathEntity pathentity, double d0) { + if (pathentity == null) { + this.c = null; +@@ -217,7 +218,7 @@ public abstract class NavigationAbstract { + } + } + +- @Nullable ++ @Nullable public PathEntity getPathEntity() { return k(); } @Nullable // Paper - OBFHELPER + public PathEntity k() { + return this.c; + } +@@ -341,6 +342,7 @@ public abstract class NavigationAbstract { + return !this.m(); + } + ++ public void stopPathfinding() { o(); } // Paper - OBFHELPER + public void o() { + this.c = null; + } +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathEntity.java b/src/main/java/net/minecraft/world/level/pathfinder/PathEntity.java +index 606027de777750f6d2ab0d7f1ef387ed4f0c6092..81c3cb9da3f901d2bcf384f7113bdc5c60f9962f 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/PathEntity.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathEntity.java +@@ -8,13 +8,14 @@ import net.minecraft.world.phys.Vec3D; + + public class PathEntity { + +- private final List a; ++ private final List a; public List getPoints() { return a; } // Paper - OBFHELPER + private PathPoint[] b = new PathPoint[0]; + private PathPoint[] c = new PathPoint[0]; +- private int e; ++ private int e; public int getNextIndex() { return this.e; } // Paper - OBFHELPER + private final BlockPosition f; + private final float g; + private final boolean h; ++ public boolean hasNext() { return getNextIndex() < getPoints().size(); } // Paper + + public PathEntity(List list, BlockPosition blockposition, boolean flag) { + this.a = list; +@@ -36,7 +37,7 @@ public class PathEntity { + } + + @Nullable +- public PathPoint d() { ++ public PathPoint getFinalPoint() { return d(); } @Nullable public PathPoint d() { // Paper - OBFHELPER + return !this.a.isEmpty() ? (PathPoint) this.a.get(this.a.size() - 1) : null; + } + +@@ -84,7 +85,7 @@ public class PathEntity { + return this.a(entity, this.e); + } + +- public BlockPosition g() { ++ public BlockPosition getNext() { return g(); } public BlockPosition g() { // Paper - OBFHELPER + return ((PathPoint) this.a.get(this.e)).a(); + } + +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathPoint.java b/src/main/java/net/minecraft/world/level/pathfinder/PathPoint.java +index 43cc9430972a18cbf03a590d576ed200e3836017..c260b0ca70cb18811158761c574aee9c3166da28 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/PathPoint.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathPoint.java +@@ -5,9 +5,9 @@ import net.minecraft.util.MathHelper; + + public class PathPoint { + +- public final int a; +- public final int b; +- public final int c; ++ public final int a; public final int getX() { return a; } // Paper - OBFHELPER ++ public final int b; public final int getY() { return b; } // Paper - OBFHELPER ++ public final int c; public final int getZ() { return c; } // Paper - OBFHELPER + private final int m; + public int d = -1; + public float e; +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathfinderAbstract.java b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderAbstract.java +index f2080bd50db04af6eabec4b4b757d6dadfb1a2f5..88be03bd77656235322522c3782b9f9a878b86b1 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/PathfinderAbstract.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderAbstract.java +@@ -16,9 +16,9 @@ public abstract class PathfinderAbstract { + protected int d; + protected int e; + protected int f; +- protected boolean g; +- protected boolean h; +- protected boolean i; ++ protected boolean g; public boolean shouldPassDoors() { return g; } public void setShouldPassDoors(boolean b) { g = b; } // Paper - obfhelper ++ protected boolean h; public boolean shouldOpenDoors() { return h; } public void setShouldOpenDoors(boolean b) { h = b; } // Paper - obfhelper ++ protected boolean i; public boolean shouldFloat() { return i; } public void setShouldFloat(boolean b) { i = b; } // Paper - obfhelper + + public PathfinderAbstract() {} + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index eb275ac45def34d5bb1ed696b4f6a4d53d282ab3..28a4e90130f51fd2fda7003fde5b4d0a410e1aef 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -12,8 +12,11 @@ import org.bukkit.loot.LootTable; + public abstract class CraftMob extends CraftLivingEntity implements Mob { + public CraftMob(CraftServer server, EntityInsentient entity) { + super(server, entity); ++ paperPathfinder = new com.destroystokyo.paper.entity.PaperPathfinder(entity); // Paper + } + ++ private final com.destroystokyo.paper.entity.PaperPathfinder paperPathfinder; // Paper ++ @Override public com.destroystokyo.paper.entity.Pathfinder getPathfinder() { return paperPathfinder; } // Paper + @Override + public void setTarget(LivingEntity target) { + EntityInsentient entity = getHandle(); diff --git a/patches/server-unmapped/0001/0284-Prevent-chunk-loading-from-Fluid-Flowing.patch b/patches/server-unmapped/0001/0284-Prevent-chunk-loading-from-Fluid-Flowing.patch new file mode 100644 index 0000000000..3b7bb307af --- /dev/null +++ b/patches/server-unmapped/0001/0284-Prevent-chunk-loading-from-Fluid-Flowing.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 10 Sep 2018 23:36:16 -0400 +Subject: [PATCH] Prevent chunk loading from Fluid Flowing + + +diff --git a/src/main/java/net/minecraft/world/level/material/FluidTypeFlowing.java b/src/main/java/net/minecraft/world/level/material/FluidTypeFlowing.java +index 2f0f8a44d808875d70123a63487ce1ebe02f53a9..6bb4ec00e40795ced73648fefcd1f5027e0113cd 100644 +--- a/src/main/java/net/minecraft/world/level/material/FluidTypeFlowing.java ++++ b/src/main/java/net/minecraft/world/level/material/FluidTypeFlowing.java +@@ -176,7 +176,8 @@ public abstract class FluidTypeFlowing extends FluidType { + EnumDirection enumdirection = (EnumDirection) entry.getKey(); + Fluid fluid1 = (Fluid) entry.getValue(); + BlockPosition blockposition1 = blockposition.shift(enumdirection); +- IBlockData iblockdata1 = generatoraccess.getType(blockposition1); ++ IBlockData iblockdata1 = generatoraccess.getTypeIfLoaded(blockposition1); // Paper ++ if (iblockdata1 == null) continue; // Paper + + if (this.a(generatoraccess, blockposition, iblockdata, enumdirection, blockposition1, iblockdata1, generatoraccess.getFluid(blockposition1), fluid1.getType())) { + // CraftBukkit start +@@ -203,7 +204,8 @@ public abstract class FluidTypeFlowing extends FluidType { + while (iterator.hasNext()) { + EnumDirection enumdirection = (EnumDirection) iterator.next(); + BlockPosition blockposition1 = blockposition.shift(enumdirection); +- IBlockData iblockdata1 = iworldreader.getType(blockposition1); ++ IBlockData iblockdata1 = iworldreader.getTypeIfLoaded(blockposition1); // Paper ++ if (iblockdata1 == null) continue; // Paper + Fluid fluid = iblockdata1.getFluid(); + + if (fluid.getType().a((FluidType) this) && this.a(enumdirection, (IBlockAccess) iworldreader, blockposition, iblockdata, blockposition1, iblockdata1)) { +@@ -320,11 +322,18 @@ public abstract class FluidTypeFlowing extends FluidType { + if (enumdirection1 != enumdirection) { + BlockPosition blockposition2 = blockposition.shift(enumdirection1); + short short0 = a(blockposition1, blockposition2); +- Pair pair = (Pair) short2objectmap.computeIfAbsent(short0, (k) -> { +- IBlockData iblockdata1 = iworldreader.getType(blockposition2); ++ // Paper start - avoid loading chunks ++ Pair pair = short2objectmap.get(short0); ++ if (pair == null) { ++ IBlockData iblockdatax = iworldreader.getTypeIfLoaded(blockposition2); ++ if (iblockdatax == null) { ++ continue; ++ } + +- return Pair.of(iblockdata1, iblockdata1.getFluid()); +- }); ++ pair = Pair.of(iblockdatax, iblockdatax.getFluid()); ++ short2objectmap.put(short0, pair); ++ } ++ // Paper end + IBlockData iblockdata1 = (IBlockData) pair.getFirst(); + Fluid fluid = (Fluid) pair.getSecond(); + +@@ -396,11 +405,16 @@ public abstract class FluidTypeFlowing extends FluidType { + EnumDirection enumdirection = (EnumDirection) iterator.next(); + BlockPosition blockposition1 = blockposition.shift(enumdirection); + short short0 = a(blockposition, blockposition1); +- Pair pair = (Pair) short2objectmap.computeIfAbsent(short0, (j) -> { +- IBlockData iblockdata1 = iworldreader.getType(blockposition1); +- +- return Pair.of(iblockdata1, iblockdata1.getFluid()); +- }); ++ // Paper start ++ Pair pair = (Pair) short2objectmap.get(short0); ++ if (pair == null) { ++ IBlockData iblockdatax = iworldreader.getTypeIfLoaded(blockposition1); ++ if (iblockdatax == null) continue; ++ ++ pair = Pair.of(iblockdatax, iblockdatax.getFluid()); ++ short2objectmap.put(short0, pair); ++ } ++ // Paper end + IBlockData iblockdata1 = (IBlockData) pair.getFirst(); + Fluid fluid = (Fluid) pair.getSecond(); + Fluid fluid1 = this.a(iworldreader, blockposition1, iblockdata1); diff --git a/patches/server-unmapped/0001/0285-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch b/patches/server-unmapped/0001/0285-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch new file mode 100644 index 0000000000..22d4952f48 --- /dev/null +++ b/patches/server-unmapped/0001/0285-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch @@ -0,0 +1,446 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Wed, 12 Sep 2018 18:53:55 +0300 +Subject: [PATCH] Implement an API for CanPlaceOn and CanDestroy NBT values + + +diff --git a/src/main/java/net/minecraft/commands/arguments/blocks/ArgumentBlock.java b/src/main/java/net/minecraft/commands/arguments/blocks/ArgumentBlock.java +index 7e53d8b787c42f8592140f7de8974bc63e5149b2..d72b800e5f03422d0b2518980b1955ec7d2b08e8 100644 +--- a/src/main/java/net/minecraft/commands/arguments/blocks/ArgumentBlock.java ++++ b/src/main/java/net/minecraft/commands/arguments/blocks/ArgumentBlock.java +@@ -57,7 +57,7 @@ public class ArgumentBlock { + private final boolean j; + private final Map, Comparable> k = Maps.newLinkedHashMap(); // CraftBukkit - stable + private final Map l = Maps.newHashMap(); +- private MinecraftKey m = new MinecraftKey(""); ++ private MinecraftKey m = new MinecraftKey(""); public final MinecraftKey getBlockKey() { return this.m; } // Paper - OBFHELPER + private BlockStateList n; + private IBlockData o; + @Nullable +@@ -86,11 +86,13 @@ public class ArgumentBlock { + return this.p; + } + ++ public final @Nullable MinecraftKey getTagKey() { return d(); } // Paper - OBFHELPER + @Nullable + public MinecraftKey d() { + return this.q; + } + ++ public final ArgumentBlock parse(boolean parseTile) throws CommandSyntaxException { return this.a(parseTile); } // Paper - OBFHELPER + public ArgumentBlock a(boolean flag) throws CommandSyntaxException { + this.s = this::l; + if (this.i.canRead() && this.i.peek() == '#') { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 7f790c484fec77e1d1f1dc6abe0daa19d009ae46..8f8dccd6fb2e49d65383d6e8f3fc5ffbabd2b7a5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -39,12 +39,14 @@ import java.util.logging.Level; + import java.util.logging.Logger; + import javax.annotation.Nonnull; + import javax.annotation.Nullable; ++import net.minecraft.commands.arguments.blocks.ArgumentBlock; + import net.minecraft.nbt.NBTBase; + import net.minecraft.nbt.NBTCompressedStreamTools; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTTagList; + import net.minecraft.nbt.NBTTagString; + import net.minecraft.network.chat.ChatComponentText; ++import net.minecraft.resources.MinecraftKey; + import net.minecraft.world.entity.EnumItemSlot; + import net.minecraft.world.item.ItemBlock; + import org.apache.commons.codec.binary.Base64; +@@ -84,6 +86,12 @@ import org.bukkit.persistence.PersistentDataContainer; + import static org.spigotmc.ValidateUtils.*; + // Spigot end + ++// Paper start ++import com.destroystokyo.paper.Namespaced; ++import com.destroystokyo.paper.NamespacedTag; ++import java.util.Collections; ++// Paper end ++ + /** + * Children must include the following: + * +@@ -267,6 +275,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + @Specific(Specific.To.NBT) + static final ItemMetaKey BLOCK_DATA = new ItemMetaKey("BlockStateTag"); + static final ItemMetaKey BUKKIT_CUSTOM_TAG = new ItemMetaKey("PublicBukkitValues"); ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ static final ItemMetaKey CAN_DESTROY = new ItemMetaKey("CanDestroy"); ++ static final ItemMetaKey CAN_PLACE_ON = new ItemMetaKey("CanPlaceOn"); ++ // Paper end + + // We store the raw original JSON representation of all text data. See SPIGOT-5063, SPIGOT-5656, SPIGOT-5304 + private String displayName; +@@ -280,6 +292,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + private int hideFlag; + private boolean unbreakable; + private int damage; ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ private Set placeableKeys = Sets.newHashSet(); ++ private Set destroyableKeys = Sets.newHashSet(); ++ // Paper end + + private static final Set HANDLED_TAGS = Sets.newHashSet(); + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); +@@ -317,6 +333,15 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.hideFlag = meta.hideFlag; + this.unbreakable = meta.unbreakable; + this.damage = meta.damage; ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (meta.hasPlaceableKeys()) { ++ this.placeableKeys = new java.util.HashSet<>(meta.placeableKeys); ++ } ++ ++ if (meta.hasDestroyableKeys()) { ++ this.destroyableKeys = new java.util.HashSet<>(meta.destroyableKeys); ++ } ++ // Paper end + this.unhandledTags.putAll(meta.unhandledTags); + this.persistentDataContainer.putAll(meta.persistentDataContainer.getRaw()); + +@@ -380,6 +405,31 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + persistentDataContainer.put(key, compound.get(key)); + } + } ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (tag.hasKey(CAN_DESTROY.NBT)) { ++ NBTTagList list = tag.getList(CAN_DESTROY.NBT, CraftMagicNumbers.NBT.TAG_STRING); ++ for (int i = 0; i < list.size(); i++) { ++ Namespaced namespaced = this.deserializeNamespaced(list.getString(i)); ++ if (namespaced == null) { ++ continue; ++ } ++ ++ this.destroyableKeys.add(namespaced); ++ } ++ } ++ ++ if (tag.hasKey(CAN_PLACE_ON.NBT)) { ++ NBTTagList list = tag.getList(CAN_PLACE_ON.NBT, CraftMagicNumbers.NBT.TAG_STRING); ++ for (int i = 0; i < list.size(); i++) { ++ Namespaced namespaced = this.deserializeNamespaced(list.getString(i)); ++ if (namespaced == null) { ++ continue; ++ } ++ ++ this.placeableKeys.add(namespaced); ++ } ++ } ++ // Paper end + + Set keys = tag.getKeys(); + for (String key : keys) { +@@ -518,6 +568,34 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + setDamage(damage); + } + ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ Iterable canPlaceOnSerialized = SerializableMeta.getObject(Iterable.class, map, CAN_PLACE_ON.BUKKIT, true); ++ if (canPlaceOnSerialized != null) { ++ for (Object canPlaceOnElement : canPlaceOnSerialized) { ++ String canPlaceOnRaw = (String) canPlaceOnElement; ++ Namespaced value = this.deserializeNamespaced(canPlaceOnRaw); ++ if (value == null) { ++ continue; ++ } ++ ++ this.placeableKeys.add(value); ++ } ++ } ++ ++ Iterable canDestroySerialized = SerializableMeta.getObject(Iterable.class, map, CAN_DESTROY.BUKKIT, true); ++ if (canDestroySerialized != null) { ++ for (Object canDestroyElement : canDestroySerialized) { ++ String canDestroyRaw = (String) canDestroyElement; ++ Namespaced value = this.deserializeNamespaced(canDestroyRaw); ++ if (value == null) { ++ continue; ++ } ++ ++ this.destroyableKeys.add(value); ++ } ++ } ++ // Paper end ++ + String internal = SerializableMeta.getString(map, "internal", true); + if (internal != null) { + ByteArrayInputStream buf = new ByteArrayInputStream(Base64.decodeBase64(internal)); +@@ -646,6 +724,23 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + if (hasDamage()) { + itemTag.setInt(DAMAGE.NBT, damage); + } ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (hasPlaceableKeys()) { ++ List items = this.placeableKeys.stream() ++ .map(this::serializeNamespaced) ++ .collect(java.util.stream.Collectors.toList()); ++ ++ itemTag.set(CAN_PLACE_ON.NBT, createNonComponentStringList(items)); ++ } ++ ++ if (hasDestroyableKeys()) { ++ List items = this.destroyableKeys.stream() ++ .map(this::serializeNamespaced) ++ .collect(java.util.stream.Collectors.toList()); ++ ++ itemTag.set(CAN_DESTROY.NBT, createNonComponentStringList(items)); ++ } ++ // Paper end + + for (Map.Entry e : unhandledTags.entrySet()) { + itemTag.set(e.getKey(), e.getValue()); +@@ -662,6 +757,21 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + ++ // Paper start ++ static NBTTagList createNonComponentStringList(List list) { ++ if (list == null || list.isEmpty()) { ++ return null; ++ } ++ ++ NBTTagList tagList = new NBTTagList(); ++ for (String value : list) { ++ tagList.add(NBTTagString.a(value)); // Paper - NBTTagString.of(String str) ++ } ++ ++ return tagList; ++ } ++ // Paper end ++ + NBTTagList createStringList(List list) { + if (list == null) { + return null; +@@ -745,7 +855,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Overridden + boolean isEmpty() { +- return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || (lore != null) || hasCustomModelData() || hasBlockData() || hasRepairCost() || !unhandledTags.isEmpty() || !persistentDataContainer.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers()); ++ return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || (lore != null) || hasCustomModelData() || hasBlockData() || hasRepairCost() || !unhandledTags.isEmpty() || !persistentDataContainer.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers() || hasPlaceableKeys() || hasDestroyableKeys()); // Paper - Implement an API for CanPlaceOn and CanDestroy NBT values + } + + // Paper start +@@ -1169,7 +1279,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + && (this.hideFlag == that.hideFlag) + && (this.isUnbreakable() == that.isUnbreakable()) + && (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage()) +- && (this.version == that.version); ++ && (this.version == that.version) ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ && (this.hasPlaceableKeys() ? that.hasPlaceableKeys() && this.placeableKeys.equals(that.placeableKeys) : !that.hasPlaceableKeys()) ++ && (this.hasDestroyableKeys() ? that.hasDestroyableKeys() && this.destroyableKeys.equals(that.destroyableKeys) : !that.hasDestroyableKeys()); ++ // Paper end + } + + /** +@@ -1204,6 +1318,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + hash = 61 * hash + (hasDamage() ? this.damage : 0); + hash = 61 * hash + (hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0); + hash = 61 * hash + version; ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ hash = 61 * hash + (hasPlaceableKeys() ? this.placeableKeys.hashCode() : 0); ++ hash = 61 * hash + (hasDestroyableKeys() ? this.destroyableKeys.hashCode() : 0); ++ // Paper end + return hash; + } + +@@ -1228,6 +1346,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + clone.unbreakable = this.unbreakable; + clone.damage = this.damage; + clone.version = this.version; ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (this.placeableKeys != null) { ++ clone.placeableKeys = Sets.newHashSet(this.placeableKeys); ++ } ++ if (this.destroyableKeys != null) { ++ clone.destroyableKeys = Sets.newHashSet(this.destroyableKeys); ++ } ++ // Paper end + return clone; + } catch (CloneNotSupportedException e) { + throw new Error(e); +@@ -1285,6 +1411,24 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + builder.put(DAMAGE.BUKKIT, damage); + } + ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (hasPlaceableKeys()) { ++ List cerealPlaceable = this.placeableKeys.stream() ++ .map(this::serializeNamespaced) ++ .collect(java.util.stream.Collectors.toList()); ++ ++ builder.put(CAN_PLACE_ON.BUKKIT, cerealPlaceable); ++ } ++ ++ if (hasDestroyableKeys()) { ++ List cerealDestroyable = this.destroyableKeys.stream() ++ .map(this::serializeNamespaced) ++ .collect(java.util.stream.Collectors.toList()); ++ ++ builder.put(CAN_DESTROY.BUKKIT, cerealDestroyable); ++ } ++ // Paper end ++ + final Map internalTags = new HashMap(unhandledTags); + serializeInternal(internalTags); + if (!internalTags.isEmpty()) { +@@ -1449,6 +1593,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + CraftMetaArmorStand.SHOW_ARMS.NBT, + CraftMetaArmorStand.SMALL.NBT, + CraftMetaArmorStand.MARKER.NBT, ++ CAN_DESTROY.NBT, ++ CAN_PLACE_ON.NBT, + // Paper end + CraftMetaCompass.LODESTONE_DIMENSION.NBT, + CraftMetaCompass.LODESTONE_POS.NBT, +@@ -1476,4 +1622,147 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + // Paper end + ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ @Override ++ @SuppressWarnings("deprecation") ++ public Set getCanDestroy() { ++ return !hasDestroyableKeys() ? Collections.emptySet() : legacyGetMatsFromKeys(this.destroyableKeys); ++ } ++ ++ @Override ++ @SuppressWarnings("deprecation") ++ public void setCanDestroy(Set canDestroy) { ++ Validate.notNull(canDestroy, "Cannot replace with null set!"); ++ legacyClearAndReplaceKeys(this.destroyableKeys, canDestroy); ++ } ++ ++ @Override ++ @SuppressWarnings("deprecation") ++ public Set getCanPlaceOn() { ++ return !hasPlaceableKeys() ? Collections.emptySet() : legacyGetMatsFromKeys(this.placeableKeys); ++ } ++ ++ @Override ++ @SuppressWarnings("deprecation") ++ public void setCanPlaceOn(Set canPlaceOn) { ++ Validate.notNull(canPlaceOn, "Cannot replace with null set!"); ++ legacyClearAndReplaceKeys(this.placeableKeys, canPlaceOn); ++ } ++ ++ @Override ++ public Set getDestroyableKeys() { ++ return !hasDestroyableKeys() ? Collections.emptySet() : Sets.newHashSet(this.destroyableKeys); ++ } ++ ++ @Override ++ public void setDestroyableKeys(Collection canDestroy) { ++ Validate.notNull(canDestroy, "Cannot replace with null collection!"); ++ Validate.isTrue(ofAcceptableType(canDestroy), "Can only use NamespacedKey or NamespacedTag objects!"); ++ this.destroyableKeys.clear(); ++ this.destroyableKeys.addAll(canDestroy); ++ } ++ ++ @Override ++ public Set getPlaceableKeys() { ++ return !hasPlaceableKeys() ? Collections.emptySet() : Sets.newHashSet(this.placeableKeys); ++ } ++ ++ @Override ++ public void setPlaceableKeys(Collection canPlaceOn) { ++ Validate.notNull(canPlaceOn, "Cannot replace with null collection!"); ++ Validate.isTrue(ofAcceptableType(canPlaceOn), "Can only use NamespacedKey or NamespacedTag objects!"); ++ this.placeableKeys.clear(); ++ this.placeableKeys.addAll(canPlaceOn); ++ } ++ ++ @Override ++ public boolean hasPlaceableKeys() { ++ return this.placeableKeys != null && !this.placeableKeys.isEmpty(); ++ } ++ ++ @Override ++ public boolean hasDestroyableKeys() { ++ return this.destroyableKeys != null && !this.destroyableKeys.isEmpty(); ++ } ++ ++ @Deprecated ++ private void legacyClearAndReplaceKeys(Collection toUpdate, Collection beingSet) { ++ if (beingSet.stream().anyMatch(Material::isLegacy)) { ++ throw new IllegalArgumentException("Set must not contain any legacy materials!"); ++ } ++ ++ toUpdate.clear(); ++ toUpdate.addAll(beingSet.stream().map(Material::getKey).collect(java.util.stream.Collectors.toSet())); ++ } ++ ++ @Deprecated ++ private Set legacyGetMatsFromKeys(Collection names) { ++ Set mats = Sets.newHashSet(); ++ for (Namespaced key : names) { ++ if (!(key instanceof org.bukkit.NamespacedKey)) { ++ continue; ++ } ++ ++ Material material = Material.matchMaterial(key.toString(), false); ++ if (material != null) { ++ mats.add(material); ++ } ++ } ++ ++ return mats; ++ } ++ ++ private @Nullable Namespaced deserializeNamespaced(String raw) { ++ boolean isTag = raw.length() > 0 && raw.codePointAt(0) == '#'; ++ ArgumentBlock blockParser = new ArgumentBlock(new com.mojang.brigadier.StringReader(raw), true); ++ try { ++ blockParser = blockParser.parse(false); ++ } catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) { ++ e.printStackTrace(); ++ return null; ++ } ++ ++ MinecraftKey key; ++ if (isTag) { ++ key = blockParser.getTagKey(); ++ } else { ++ key = blockParser.getBlockKey(); ++ } ++ ++ if (key == null) { ++ return null; ++ } ++ ++ // don't DC the player if something slips through somehow ++ Namespaced resource = null; ++ try { ++ if (isTag) { ++ resource = new NamespacedTag(key.getNamespace(), key.getKey()); ++ } else { ++ resource = CraftNamespacedKey.fromMinecraft(key); ++ } ++ } catch (IllegalArgumentException ex) { ++ org.bukkit.Bukkit.getLogger().warning("Namespaced resource does not validate: " + key.toString()); ++ ex.printStackTrace(); ++ } ++ ++ return resource; ++ } ++ ++ private @Nonnull String serializeNamespaced(Namespaced resource) { ++ return resource.toString(); ++ } ++ ++ // not a fan of this ++ private boolean ofAcceptableType(Collection namespacedResources) { ++ ++ for (Namespaced resource : namespacedResources) { ++ if (!(resource instanceof org.bukkit.NamespacedKey || resource instanceof com.destroystokyo.paper.NamespacedTag)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0286-Prevent-Mob-AI-Rules-from-Loading-Chunks.patch b/patches/server-unmapped/0001/0286-Prevent-Mob-AI-Rules-from-Loading-Chunks.patch new file mode 100644 index 0000000000..29f93fd83f --- /dev/null +++ b/patches/server-unmapped/0001/0286-Prevent-Mob-AI-Rules-from-Loading-Chunks.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 10 Sep 2018 23:56:36 -0400 +Subject: [PATCH] Prevent Mob AI Rules from Loading Chunks + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalRemoveBlock.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalRemoveBlock.java +index b5c7b39a49afae1089a293b9b06bdd94deed1f64..61a62c093b24c43064f116630d85096159e082d3 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalRemoveBlock.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalRemoveBlock.java +@@ -30,11 +30,13 @@ public class PathfinderGoalRemoveBlock extends PathfinderGoalGotoTarget { + private final Block g; + private final EntityInsentient entity; + private int i; ++ private World world; // Paper + + public PathfinderGoalRemoveBlock(Block block, EntityCreature entitycreature, double d0, int i) { + super(entitycreature, d0, 24, i); + this.g = block; + this.entity = entitycreature; ++ this.world = entitycreature.world; // Paper + } + + @Override +@@ -132,7 +134,9 @@ public class PathfinderGoalRemoveBlock extends PathfinderGoalGotoTarget { + + @Nullable + private BlockPosition a(BlockPosition blockposition, IBlockAccess iblockaccess) { +- if (iblockaccess.getType(blockposition).a(this.g)) { ++ Block block = world.getBlockIfLoaded(blockposition); // Paper ++ if (block == null) return null; // Paper ++ if (block.a(this.g)) { // Paper + return blockposition; + } else { + BlockPosition[] ablockposition = new BlockPosition[]{blockposition.down(), blockposition.west(), blockposition.east(), blockposition.north(), blockposition.south(), blockposition.down().down()}; +@@ -142,7 +146,7 @@ public class PathfinderGoalRemoveBlock extends PathfinderGoalGotoTarget { + for (int j = 0; j < i; ++j) { + BlockPosition blockposition1 = ablockposition1[j]; + +- if (iblockaccess.getType(blockposition1).a(this.g)) { ++ if (iblockaccess.getBlockIfLoaded(blockposition1).a(this.g)) { // Paper + return blockposition1; + } + } +@@ -153,7 +157,7 @@ public class PathfinderGoalRemoveBlock extends PathfinderGoalGotoTarget { + + @Override + protected boolean a(IWorldReader iworldreader, BlockPosition blockposition) { +- IChunkAccess ichunkaccess = iworldreader.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4, ChunkStatus.FULL, false); ++ IChunkAccess ichunkaccess = iworldreader.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); // Paper + + return ichunkaccess == null ? false : ichunkaccess.getType(blockposition).a(this.g) && ichunkaccess.getType(blockposition.up()).isAir() && ichunkaccess.getType(blockposition.up(2)).isAir(); + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/util/RandomPositionGenerator.java b/src/main/java/net/minecraft/world/entity/ai/util/RandomPositionGenerator.java +index 129ea3857969ddb99e15ae817ee3eec67b4c3ccf..f9c40c8223109a9a40e7e7523c8f1f2e5aeddba1 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/util/RandomPositionGenerator.java ++++ b/src/main/java/net/minecraft/world/entity/ai/util/RandomPositionGenerator.java +@@ -13,6 +13,7 @@ import net.minecraft.util.MathHelper; + import net.minecraft.world.entity.EntityCreature; + import net.minecraft.world.entity.ai.navigation.NavigationAbstract; + import net.minecraft.world.level.IBlockAccess; ++import net.minecraft.world.level.material.Fluid; + import net.minecraft.world.level.pathfinder.PathType; + import net.minecraft.world.level.pathfinder.PathfinderNormal; + import net.minecraft.world.phys.Vec3D; +@@ -128,6 +129,7 @@ public class RandomPositionGenerator { + } + + blockposition2 = new BlockPosition((double) k1 + entitycreature.locX(), (double) l1 + entitycreature.locY(), (double) i2 + entitycreature.locZ()); ++ if (!entitycreature.world.isLoaded(blockposition2)) continue; // Paper + if (blockposition2.getY() >= 0 && blockposition2.getY() <= entitycreature.world.getBuildHeight() && (!flag3 || entitycreature.a(blockposition2)) && (!flag2 || navigationabstract.a(blockposition2))) { + if (flag1) { + blockposition2 = a(blockposition2, random.nextInt(l + 1) + i1, entitycreature.world.getBuildHeight(), (blockposition3) -> { +@@ -135,7 +137,8 @@ public class RandomPositionGenerator { + }); + } + +- if (flag || !entitycreature.world.getFluid(blockposition2).a((Tag) TagsFluid.WATER)) { ++ Fluid fluid = entitycreature.world.getFluidIfLoaded(blockposition2); // Paper ++ if (flag || (fluid != null && !fluid.a((Tag) TagsFluid.WATER))) { // Paper + PathType pathtype = PathfinderNormal.a((IBlockAccess) entitycreature.world, blockposition2.i()); + + if (entitycreature.a(pathtype) == 0.0F) { diff --git a/patches/server-unmapped/0001/0287-Prevent-mob-spawning-from-loading-generating-chunks.patch b/patches/server-unmapped/0001/0287-Prevent-mob-spawning-from-loading-generating-chunks.patch new file mode 100644 index 0000000000..7a20915052 --- /dev/null +++ b/patches/server-unmapped/0001/0287-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/SpawnerCreature.java b/src/main/java/net/minecraft/world/level/SpawnerCreature.java +index 1969d1002b3182338614a2be0519fcdc385b7a44..5307488fa48ffa91446dd4457de1ce6a8f61da61 100644 +--- a/src/main/java/net/minecraft/world/level/SpawnerCreature.java ++++ b/src/main/java/net/minecraft/world/level/SpawnerCreature.java +@@ -176,9 +176,9 @@ public final class SpawnerCreature { + StructureManager structuremanager = worldserver.getStructureManager(); + ChunkGenerator chunkgenerator = worldserver.getChunkProvider().getChunkGenerator(); + int i = blockposition.getY(); +- IBlockData iblockdata = ichunkaccess.getType(blockposition); ++ IBlockData iblockdata = worldserver.getTypeIfLoadedAndInBounds(blockposition); // Paper - don't load chunks for mob spawn + +- if (!iblockdata.isOccluding(ichunkaccess, blockposition)) { ++ if (iblockdata != null && !iblockdata.isOccluding(ichunkaccess, blockposition)) { // Paper - don't load chunks for mob spawn + BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); + int j = 0; + int k = 0; +@@ -207,7 +207,7 @@ public final class SpawnerCreature { + if (entityhuman != null) { + double d2 = entityhuman.h(d0, (double) i, d1); + +- if (a(worldserver, ichunkaccess, blockposition_mutableblockposition, d2)) { ++ if (a(worldserver, ichunkaccess, blockposition_mutableblockposition, d2) && worldserver.isLoadedAndInBounds(blockposition_mutableblockposition)) { // Paper - don't load chunks for mob spawn + if (biomesettingsmobs_c == null) { + biomesettingsmobs_c = a(worldserver, structuremanager, chunkgenerator, enumcreaturetype, worldserver.random, (BlockPosition) blockposition_mutableblockposition); + if (biomesettingsmobs_c == null) { diff --git a/patches/server-unmapped/0001/0288-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch b/patches/server-unmapped/0001/0288-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch new file mode 100644 index 0000000000..681fbbbb31 --- /dev/null +++ b/patches/server-unmapped/0001/0288-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch @@ -0,0 +1,92 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 12 Sep 2018 21:47:01 -0400 +Subject: [PATCH] Optimize Biome Mob Lookups for Mob Spawning + +Uses an EnumMap as well as a Set paired List for O(1) contains calls. + +diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeSettingsMobs.java b/src/main/java/net/minecraft/world/level/biome/BiomeSettingsMobs.java +index 5adaf5fdaaec25220878213df2c0839ccf025d63..233ae33b5cbf1aafc7d2632149ccb84c0b243162 100644 +--- a/src/main/java/net/minecraft/world/level/biome/BiomeSettingsMobs.java ++++ b/src/main/java/net/minecraft/world/level/biome/BiomeSettingsMobs.java +@@ -30,18 +30,27 @@ public class BiomeSettingsMobs { + }, (enumcreaturetype) -> { + return ImmutableList.of(); + })), ImmutableMap.of(), false); ++ // Paper start- decompile error workaround ++ private static class bProxy extends BiomeSettingsMobs.b { ++ private bProxy(double d0, double d1) { ++ super(d0, d1); ++ } ++ } ++ private static class cProxy extends BiomeSettingsMobs.c { ++ public cProxy(EntityTypes entitytypes, int i, int j, int k) { ++ super(entitytypes, i, j, k); ++ } ++ }; ++ // Paper end + public static final MapCodec c = RecordCodecBuilder.mapCodec((instance) -> { +- RecordCodecBuilder recordcodecbuilder = Codec.FLOAT.optionalFieldOf("creature_spawn_probability", 0.1F).forGetter((biomesettingsmobs) -> { ++ RecordCodecBuilder recordcodecbuilder = Codec.FLOAT.optionalFieldOf("creature_spawn_probability", 0.1F).forGetter((biomesettingsmobs) -> { // Paper - add type to builder + return biomesettingsmobs.d; + }); +- Codec codec = EnumCreatureType.g; +- Codec codec1 = BiomeSettingsMobs.c.b.listOf(); +- Logger logger = BiomeSettingsMobs.LOGGER; ++ // Paper - remove unused vars + +- logger.getClass(); +- return instance.group(recordcodecbuilder, Codec.simpleMap(codec, codec1.promotePartial(SystemUtils.a("Spawn data: ", logger::error)), INamable.a(EnumCreatureType.values())).fieldOf("spawners").forGetter((biomesettingsmobs) -> { ++ return instance.group(recordcodecbuilder, Codec.simpleMap(EnumCreatureType.g, cProxy.b.listOf().promotePartial(SystemUtils.a("Spawn data: ", BiomeSettingsMobs.LOGGER::error)), INamable.a(EnumCreatureType.values())).fieldOf("spawners").forGetter((biomesettingsmobs) -> { // Paper - inline codec, cProxy, LOGGER + return biomesettingsmobs.e; +- }), Codec.simpleMap(IRegistry.ENTITY_TYPE, BiomeSettingsMobs.b.a, IRegistry.ENTITY_TYPE).fieldOf("spawn_costs").forGetter((biomesettingsmobs) -> { ++ }), Codec.simpleMap(IRegistry.ENTITY_TYPE, bProxy.a, IRegistry.ENTITY_TYPE).fieldOf("spawn_costs").forGetter((biomesettingsmobs) -> { // Paper - decompile error - bProxy + return biomesettingsmobs.f; + }), Codec.BOOL.fieldOf("player_spawn_friendly").orElse(false).forGetter(BiomeSettingsMobs::b)).apply(instance, BiomeSettingsMobs::new); + }); +@@ -76,11 +85,43 @@ public class BiomeSettingsMobs { + + public static class a { + +- private final Map> a = (Map) Stream.of(EnumCreatureType.values()).collect(ImmutableMap.toImmutableMap((enumcreaturetype) -> { ++ // Paper start - keep track of data in a pair set to give O(1) contains calls - we have to hook removals incase plugins mess with it ++ public static class MobList extends java.util.ArrayList { ++ java.util.Set biomes = new java.util.HashSet<>(); ++ ++ @Override ++ public boolean contains(Object o) { ++ return biomes.contains(o); ++ } ++ ++ @Override ++ public boolean add(BiomeSettingsMobs.c BiomeSettingsMobs) { ++ biomes.add(BiomeSettingsMobs); ++ return super.add(BiomeSettingsMobs); ++ } ++ ++ @Override ++ public BiomeSettingsMobs.c remove(int index) { ++ BiomeSettingsMobs.c removed = super.remove(index); ++ if (removed != null) { ++ biomes.remove(removed); ++ } ++ return removed; ++ } ++ ++ @Override ++ public void clear() { ++ biomes.clear(); ++ super.clear(); ++ } ++ } ++ // use toImmutableEnumMap collector ++ private final Map> a = (Map) Stream.of(EnumCreatureType.values()).collect(Maps.toImmutableEnumMap((enumcreaturetype) -> { + return enumcreaturetype; + }, (enumcreaturetype) -> { +- return Lists.newArrayList(); ++ return new MobList(); // Use MobList instead of ArrayList + })); ++ // Paper end + private final Map, BiomeSettingsMobs.b> b = Maps.newLinkedHashMap(); + private float c = 0.1F; + private boolean d; diff --git a/patches/server-unmapped/0001/0289-Implement-furnace-cook-speed-multiplier-API.patch b/patches/server-unmapped/0001/0289-Implement-furnace-cook-speed-multiplier-API.patch new file mode 100644 index 0000000000..110e0093a4 --- /dev/null +++ b/patches/server-unmapped/0001/0289-Implement-furnace-cook-speed-multiplier-API.patch @@ -0,0 +1,102 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tassu +Date: Thu, 13 Sep 2018 08:45:21 +0300 +Subject: [PATCH] Implement furnace cook speed multiplier API + +Signed-off-by: Tassu + +Fixed an issue where a furnace's cook-speed multiplier rounds down +to the nearest Integer when updating its current cook time. + +Modified by: Eric Su + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +index deaa4c136c23dc6c258cc1ce68523b3c007c80f9..e630e8d3e115d2a0177849ad8258a2304b9d3e9d 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +@@ -40,6 +40,7 @@ import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.phys.Vec3D; + + // CraftBukkit start ++import java.util.List; + import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.craftbukkit.entity.CraftHumanEntity; + import org.bukkit.craftbukkit.inventory.CraftItemStack; +@@ -58,6 +59,7 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + protected NonNullList items; + public int burnTime; + private int ticksForCurrentFuel; ++ public double cookSpeedMultiplier = 1.0; // Paper - cook speed multiplier API + public int cookTime; + public int cookTimeTotal; + protected final IContainerProperties b; +@@ -258,6 +260,11 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + this.n.put(new MinecraftKey(s), nbttagcompound1.getInt(s)); + } + ++ // Paper start - cook speed API ++ if (nbttagcompound.hasKey("Paper.CookSpeedMultiplier")) { ++ this.cookSpeedMultiplier = nbttagcompound.getDouble("Paper.CookSpeedMultiplier"); ++ } ++ // Paper end + } + + @Override +@@ -266,6 +273,7 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + nbttagcompound.setShort("BurnTime", (short) this.burnTime); + nbttagcompound.setShort("CookTime", (short) this.cookTime); + nbttagcompound.setShort("CookTimeTotal", (short) this.cookTimeTotal); ++ nbttagcompound.setDouble("Paper.CookSpeedMultiplier", this.cookSpeedMultiplier); // Paper - cook speed multiplier API + ContainerUtil.a(nbttagcompound, this.items); + NBTTagCompound nbttagcompound1 = new NBTTagCompound(); + +@@ -326,7 +334,7 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + + if (this.isBurning() && this.canBurn(irecipe)) { + ++this.cookTime; +- if (this.cookTime == this.cookTimeTotal) { ++ if (this.cookTime >= this.cookTimeTotal) { // Paper - cook speed multiplier API + this.cookTime = 0; + this.cookTimeTotal = this.getRecipeCookingTime(); + this.burn(irecipe); +@@ -426,9 +434,13 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + } + } + +- protected int getRecipeCookingTime() { +- return (this.hasWorld()) ? (Integer) this.world.getCraftingManager().craft((Recipes) this.c, this, this.world).map(RecipeCooking::getCookingTime).orElse(200) : 200; // CraftBukkit - SPIGOT-4302 // Eclipse fail ++ // Paper begin - Expose this function so CraftFurnace can correctly scale the total cooking time to a new multiplier ++ public int getRecipeCookingTime() { ++ /* Scale the recipe's cooking time to the current cookSpeedMultiplier */ ++ int cookTime = (this.hasWorld()) ? (Integer) this.world.getCraftingManager().craft((Recipes) this.c, this, this.world).map(RecipeCooking::getCookingTime).orElse(200) : 200; // CraftBukkit - SPIGOT-4302 // Eclipse fail ++ return (int) Math.ceil (cookTime / this.cookSpeedMultiplier); + } ++ // Paper end + + public static boolean isFuel(ItemStack itemstack) { + return f().containsKey(itemstack.getItem()); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java +index 760fe719f2f8836c41f6a49d8a795703b2474390..86ad5f78eecc01863897838cb1f311f5dd3d996d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java +@@ -63,4 +63,20 @@ public abstract class CraftFurnace extends CraftCon + public void setCookTimeTotal(int cookTimeTotal) { + this.getSnapshot().cookTimeTotal = cookTimeTotal; + } ++ ++ // Paper start - cook speed multiplier API ++ @Override ++ public double getCookSpeedMultiplier() { ++ return this.getSnapshot().cookSpeedMultiplier; ++ } ++ ++ @Override ++ public void setCookSpeedMultiplier(double multiplier) { ++ com.google.common.base.Preconditions.checkArgument(multiplier >= 0, "Furnace speed multiplier cannot be negative"); ++ com.google.common.base.Preconditions.checkArgument(multiplier <= 200, "Furnace speed multiplier cannot more than 200"); ++ T snapshot = this.getSnapshot(); ++ snapshot.cookSpeedMultiplier = multiplier; ++ snapshot.cookTimeTotal = snapshot.getRecipeCookingTime(); // Update the snapshot's current total cook time to scale with the newly set multiplier ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0290-PreSpawnerSpawnEvent.patch b/patches/server-unmapped/0001/0290-PreSpawnerSpawnEvent.patch new file mode 100644 index 0000000000..df8c61a859 --- /dev/null +++ b/patches/server-unmapped/0001/0290-PreSpawnerSpawnEvent.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Tue, 18 Sep 2018 23:53:23 +0100 +Subject: [PATCH] PreSpawnerSpawnEvent + +This adds a separate event before an entity is spawned by a spawner +which contains the location of the spawner too similarly to how the +SpawnerSpawnEvent gets called instead of the CreatureSpawnEvent for +spawners. + +diff --git a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +index d4b8126f12fdf7d9b4f882d3ed7d8da544ed9e8a..867478484c0ba4ff467b96e458689937299b981d 100644 +--- a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java ++++ b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +@@ -132,11 +132,11 @@ public abstract class MobSpawnerAbstract { + + org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(key); + if (type != null) { +- com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; +- event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event; ++ event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent( + MCUtil.toLocation(world, d3, d4, d5), + type, +- org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER ++ MCUtil.toLocation(world, blockposition) + ); + if (!event.callEvent()) { + flag = true; diff --git a/patches/server-unmapped/0001/0291-Catch-JsonParseException-in-Entity-and-TE-names.patch b/patches/server-unmapped/0001/0291-Catch-JsonParseException-in-Entity-and-TE-names.patch new file mode 100644 index 0000000000..21a0e7b36d --- /dev/null +++ b/patches/server-unmapped/0001/0291-Catch-JsonParseException-in-Entity-and-TE-names.patch @@ -0,0 +1,110 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 22 Sep 2018 15:56:59 -0400 +Subject: [PATCH] Catch JsonParseException in Entity and TE names + +As a result, data that no longer parses correctly will not crash the server +instead just logging the exception and continuing (and in most cases should +fix the data) + +Player data is fixed pretty much immediately but some block data (like +Shulkers) may need to be changed in order for it to re-save properly + +No more crashing though. + +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index cd7dc7d90efddb8a1bb50cd964b43d18cf9c83d1..35d1444c5b75d9a3a6cface5dd70aea0a08ac89d 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -7,6 +7,8 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; + import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.server.level.WorldServer; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.ChunkCoordIntPair; +@@ -514,4 +516,19 @@ public final class MCUtil { + return null; + } + } ++ ++ @Nullable ++ public static IChatBaseComponent getBaseComponentFromNbt(String key, NBTTagCompound compound) { ++ if (!compound.hasKey(key)) { ++ return null; ++ } ++ String string = compound.getString(key); ++ try { ++ return IChatBaseComponent.ChatSerializer.jsonToComponent(string); ++ } catch (com.google.gson.JsonParseException e) { ++ org.bukkit.Bukkit.getLogger().warning("Unable to parse " + key + " from " + compound +": " + e.getMessage()); ++ } ++ ++ return null; ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/CommandBlockListenerAbstract.java b/src/main/java/net/minecraft/world/level/CommandBlockListenerAbstract.java +index 94adf4d3b3a367e2a7fa383f1da6fb3b02b35c85..3fcdff3649c725580456dfc965d6c83bd5afe3da 100644 +--- a/src/main/java/net/minecraft/world/level/CommandBlockListenerAbstract.java ++++ b/src/main/java/net/minecraft/world/level/CommandBlockListenerAbstract.java +@@ -12,6 +12,7 @@ import net.minecraft.commands.ICommandListener; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.chat.ChatComponentText; + import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.WorldServer; + import net.minecraft.util.UtilColor; +@@ -72,7 +73,7 @@ public abstract class CommandBlockListenerAbstract implements ICommandListener { + this.command = nbttagcompound.getString("Command"); + this.successCount = nbttagcompound.getInt("SuccessCount"); + if (nbttagcompound.hasKeyOfType("CustomName", 8)) { +- this.setName(IChatBaseComponent.ChatSerializer.a(nbttagcompound.getString("CustomName"))); ++ this.setName(MCUtil.getBaseComponentFromNbt("CustomName", nbttagcompound)); // Paper - Catch ParseException + } + + if (nbttagcompound.hasKeyOfType("TrackOutput", 1)) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBanner.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBanner.java +index fd8d39d04f39ea8aa389deb66ca0ddaa3e282c40..45958ffedca64e08e347ae65033700c0d798beb5 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBanner.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBanner.java +@@ -9,6 +9,7 @@ import net.minecraft.nbt.NBTTagList; + import net.minecraft.network.chat.ChatMessage; + import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.network.protocol.game.PacketPlayOutTileEntityData; ++import net.minecraft.server.MCUtil; + import net.minecraft.world.INamableTileEntity; + import net.minecraft.world.item.EnumColor; + import net.minecraft.world.item.ItemStack; +@@ -70,7 +71,7 @@ public class TileEntityBanner extends TileEntity implements INamableTileEntity { + public void load(IBlockData iblockdata, NBTTagCompound nbttagcompound) { + super.load(iblockdata, nbttagcompound); + if (nbttagcompound.hasKeyOfType("CustomName", 8)) { +- this.a = IChatBaseComponent.ChatSerializer.a(nbttagcompound.getString("CustomName")); ++ this.a = MCUtil.getBaseComponentFromNbt("CustomName", nbttagcompound); // Paper - Catch ParseException + } + + if (this.hasWorld()) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityContainer.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityContainer.java +index 19739ad1fb01c767288da2667a48909e4c1c36cc..fb7a1a854efcf42f0351ef521aff67d5fcc4ab27 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityContainer.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityContainer.java +@@ -4,6 +4,7 @@ import javax.annotation.Nullable; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.chat.ChatMessage; + import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundCategory; + import net.minecraft.sounds.SoundEffects; + import net.minecraft.world.ChestLock; +@@ -30,7 +31,7 @@ public abstract class TileEntityContainer extends TileEntity implements IInvento + super.load(iblockdata, nbttagcompound); + this.chestLock = ChestLock.b(nbttagcompound); + if (nbttagcompound.hasKeyOfType("CustomName", 8)) { +- this.customName = IChatBaseComponent.ChatSerializer.a(nbttagcompound.getString("CustomName")); ++ this.customName = MCUtil.getBaseComponentFromNbt("CustomName", nbttagcompound); // Paper - Catch ParseException + } + + } diff --git a/patches/server-unmapped/0001/0292-Honor-EntityAgeable.ageLock.patch b/patches/server-unmapped/0001/0292-Honor-EntityAgeable.ageLock.patch new file mode 100644 index 0000000000..e69ce43f71 --- /dev/null +++ b/patches/server-unmapped/0001/0292-Honor-EntityAgeable.ageLock.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 23 Sep 2018 20:59:53 -0500 +Subject: [PATCH] Honor EntityAgeable.ageLock + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityAgeable.java b/src/main/java/net/minecraft/world/entity/EntityAgeable.java +index ea356ff0f91083195899cc2bb84b2fde504163b4..850135582c41893823c43a78a016c7791755b8b5 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityAgeable.java ++++ b/src/main/java/net/minecraft/world/entity/EntityAgeable.java +@@ -82,6 +82,7 @@ public abstract class EntityAgeable extends EntityCreature { + } + + public void setAge(int i, boolean flag) { ++ if (ageLocked) return; // Paper - GH-1459 + int j = this.getAge(); + int k = j; + diff --git a/patches/server-unmapped/0001/0293-Configurable-connection-throttle-kick-message.patch b/patches/server-unmapped/0001/0293-Configurable-connection-throttle-kick-message.patch new file mode 100644 index 0000000000..cd58fc6eac --- /dev/null +++ b/patches/server-unmapped/0001/0293-Configurable-connection-throttle-kick-message.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 2 Oct 2018 09:57:50 +0100 +Subject: [PATCH] Configurable connection throttle kick message + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 62621562137cba4804f0465c58d25ca2786328e5..7178b37f7978c7e9031a22726005c5099fd78fe0 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -281,6 +281,11 @@ public class PaperConfig { + authenticationServersDownKickMessage = Strings.emptyToNull(getString("messages.kick.authentication-servers-down", authenticationServersDownKickMessage)); + } + ++ public static String connectionThrottleKickMessage = "Connection throttled! Please wait before reconnecting."; ++ private static void connectionThrottleKickMessage() { ++ connectionThrottleKickMessage = getString("messages.kick.connection-throttle", connectionThrottleKickMessage); ++ } ++ + private static void savePlayerData() { + Object val = config.get("settings.save-player-data"); + if (val instanceof Boolean) { +diff --git a/src/main/java/net/minecraft/server/network/HandshakeListener.java b/src/main/java/net/minecraft/server/network/HandshakeListener.java +index 965dd2a4a446ca31597b9e91bd405e4f01ad8348..6d001843d8f899e91f19c384ddf57e6987bfb79e 100644 +--- a/src/main/java/net/minecraft/server/network/HandshakeListener.java ++++ b/src/main/java/net/minecraft/server/network/HandshakeListener.java +@@ -46,7 +46,7 @@ public class HandshakeListener implements PacketHandshakingInListener { + synchronized (throttleTracker) { + if (throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - throttleTracker.get(address) < connectionThrottle) { + throttleTracker.put(address, currentTime); +- ChatMessage chatmessage = new ChatMessage("Connection throttled! Please wait before reconnecting."); ++ ChatMessage chatmessage = new ChatMessage(com.destroystokyo.paper.PaperConfig.connectionThrottleKickMessage); // Paper - Configurable connection throttle kick message + this.c.sendPacket(new PacketLoginOutDisconnect(chatmessage)); + this.c.close(chatmessage); + return; diff --git a/patches/server-unmapped/0001/0294-Hook-into-CB-plugin-rewrites.patch b/patches/server-unmapped/0001/0294-Hook-into-CB-plugin-rewrites.patch new file mode 100644 index 0000000000..e8c152bdfa --- /dev/null +++ b/patches/server-unmapped/0001/0294-Hook-into-CB-plugin-rewrites.patch @@ -0,0 +1,184 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 3 Oct 2018 20:09:18 -0400 +Subject: [PATCH] Hook into CB plugin rewrites + +Allows us to do fun stuff like rewrite the OBC util fastutil location to +our own relocation. Also lets us rewrite NMS calls for when we're +debugging in an IDE pre-relocate. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +index 6ccfd49c43d0e896ee0f15dfbe577a541367dab8..21f265e3c6d9952f9e0e9a28f8c8de9ee733bf1e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +@@ -6,7 +6,9 @@ import java.io.FileOutputStream; + import java.io.InputStream; + import java.util.Arrays; + import java.util.Enumeration; ++import java.util.HashMap; + import java.util.HashSet; ++import java.util.Map; + import java.util.Set; + import java.util.jar.JarEntry; + import java.util.jar.JarFile; +@@ -20,10 +22,15 @@ import org.bukkit.plugin.AuthorNagException; + import org.objectweb.asm.ClassReader; + import org.objectweb.asm.ClassVisitor; + import org.objectweb.asm.ClassWriter; ++import org.objectweb.asm.FieldVisitor; ++import org.objectweb.asm.Handle; ++import org.objectweb.asm.Label; + import org.objectweb.asm.MethodVisitor; + import org.objectweb.asm.Opcodes; + import org.objectweb.asm.Type; + ++import javax.annotation.Nonnull; ++ + /** + * This file is imported from Commodore. + * +@@ -46,6 +53,42 @@ public class Commodore + "org/bukkit/inventory/ItemStack (I)V setTypeId" + ) ); + ++ // Paper start - Plugin rewrites ++ private static final Map SEARCH_AND_REMOVE = initReplacementsMap(); ++ private static Map initReplacementsMap() ++ { ++ Map getAndRemove = new HashMap<>(); ++ // Be wary of maven shade's relocations ++ getAndRemove.put( "org/bukkit/".concat( "craftbukkit/libs/it/unimi/dsi/fastutil/" ), "org/bukkit/".concat( "craftbukkit/libs/" ) ); // Remap fastutil to our location ++ ++ if ( Boolean.getBoolean( "debug.rewriteForIde" ) ) ++ { ++ // unversion incoming calls for pre-relocate debug work ++ final String NMS_REVISION_PACKAGE = "v1_16_R3/"; ++ ++ getAndRemove.put( "net/minecraft/".concat( "server/" + NMS_REVISION_PACKAGE ), NMS_REVISION_PACKAGE ); ++ getAndRemove.put( "org/bukkit/".concat( "craftbukkit/" + NMS_REVISION_PACKAGE ), NMS_REVISION_PACKAGE ); ++ } ++ ++ return getAndRemove; ++ } ++ ++ @Nonnull ++ private static String getOriginalOrRewrite(@Nonnull String original) ++ { ++ String rewrite = null; ++ for ( Map.Entry entry : SEARCH_AND_REMOVE.entrySet() ) ++ { ++ if ( original.contains( entry.getKey() ) ) ++ { ++ rewrite = original.replace( entry.getValue(), "" ); ++ } ++ } ++ ++ return rewrite != null ? rewrite : original; ++ } ++ // Paper end ++ + public static void main(String[] args) + { + OptionParser parser = new OptionParser(); +@@ -130,15 +173,86 @@ public class Commodore + + cr.accept( new ClassVisitor( Opcodes.ASM9, cw ) + { ++ // Paper start - Rewrite plugins ++ @Override ++ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) ++ { ++ desc = getOriginalOrRewrite( desc ); ++ if ( signature != null ) { ++ signature = getOriginalOrRewrite( signature ); ++ } ++ ++ return super.visitField( access, name, desc, signature, value) ; ++ } ++ // Paper end ++ + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) + { + return new MethodVisitor( api, super.visitMethod( access, name, desc, signature, exceptions ) ) + { ++ // Paper start - Plugin rewrites ++ @Override ++ public void visitInvokeDynamicInsn(String name, String desc, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) ++ { ++ // Paper start - Rewrite plugins ++ name = getOriginalOrRewrite( name ); ++ if ( desc != null ) ++ { ++ desc = getOriginalOrRewrite( desc ); ++ } ++ // Paper end ++ ++ super.visitInvokeDynamicInsn( name, desc, bootstrapMethodHandle, bootstrapMethodArguments ); ++ } ++ ++ @Override ++ public void visitTypeInsn(int opcode, String type) ++ { ++ type = getOriginalOrRewrite( type ); ++ ++ super.visitTypeInsn( opcode, type ); ++ } ++ ++ @Override ++ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { ++ for ( int i = 0; i < local.length; i++ ) ++ { ++ if ( !( local[i] instanceof String ) ) { continue; } ++ ++ local[i] = getOriginalOrRewrite( (String) local[i] ); ++ } ++ ++ for ( int i = 0; i < stack.length; i++ ) ++ { ++ if ( !( stack[i] instanceof String ) ) { continue; } ++ ++ stack[i] = getOriginalOrRewrite( (String) stack[i] ); ++ } ++ ++ super.visitFrame( type, nLocal, local, nStack, stack ); ++ } ++ ++ @Override ++ public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) ++ { ++ descriptor = getOriginalOrRewrite( descriptor ); ++ ++ super.visitLocalVariable( name, descriptor, signature, start, end, index ); ++ } ++ // Paper end + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) + { ++ // Paper start - Rewrite plugins ++ owner = getOriginalOrRewrite( owner ); ++ if ( desc != null ) ++ { ++ desc = getOriginalOrRewrite( desc ); ++ } ++ // Paper end ++ + if ( owner.equals( "org/bukkit/block/Biome" ) ) + { + switch ( name ) +@@ -260,6 +374,14 @@ public class Commodore + return; + } + ++ // Paper start - Rewrite plugins ++ owner = getOriginalOrRewrite( owner) ; ++ if (desc != null) ++ { ++ desc = getOriginalOrRewrite(desc); ++ } ++ // Paper end ++ + if ( modern ) + { + if ( owner.equals( "org/bukkit/Material" ) ) diff --git a/patches/server-unmapped/0001/0295-Allow-setting-the-vex-s-summoner.patch b/patches/server-unmapped/0001/0295-Allow-setting-the-vex-s-summoner.patch new file mode 100644 index 0000000000..d97a31a735 --- /dev/null +++ b/patches/server-unmapped/0001/0295-Allow-setting-the-vex-s-summoner.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 6 Oct 2018 21:47:44 -0500 +Subject: [PATCH] Allow setting the vex's summoner + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityVex.java b/src/main/java/net/minecraft/world/entity/monster/EntityVex.java +index 3b74ade60b3b0ae0e908866cb4ac11acd75620ff..9645d052069957311478a1ceca42ad52f7a9aa0b 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityVex.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityVex.java +@@ -165,6 +165,7 @@ public class EntityVex extends EntityMonster { + this.a(1, flag); + } + ++ public void setOwner(EntityInsentient entityinsentient) { a(entityinsentient); } // Paper - OBFHELPER + public void a(EntityInsentient entityinsentient) { + this.c = entityinsentient; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +index 962d6017f6acc47ebe4b754ccd9b97a1fc97cc58..30e2c169388b09b94d801be7543e75ea0bd56fcd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +@@ -21,6 +21,10 @@ public class CraftVex extends CraftMonster implements Vex { + net.minecraft.world.entity.EntityInsentient owner = getHandle().getOwner(); + return owner != null ? (org.bukkit.entity.Mob) owner.getBukkitEntity() : null; + } ++ ++ public void setSummoner(org.bukkit.entity.Mob summoner) { ++ getHandle().setOwner(summoner == null ? null : ((CraftMob) summoner).getHandle()); ++ } + // Paper end + + @Override diff --git a/patches/server-unmapped/0001/0296-Add-sun-related-API.patch b/patches/server-unmapped/0001/0296-Add-sun-related-API.patch new file mode 100644 index 0000000000..d88e5b7aec --- /dev/null +++ b/patches/server-unmapped/0001/0296-Add-sun-related-API.patch @@ -0,0 +1,52 @@ +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 + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +index 41566398f5eee6cf93376f2e2200728bb6d2181c..db72b685f4a4b95f345f1d34f9eeb83b8731120a 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java ++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +@@ -1597,6 +1597,7 @@ public abstract class EntityInsentient extends EntityLiving { + + } + ++ public boolean isInDaylight() { return this.eG(); } // Paper - OBFHELPER + protected boolean eG() { + if (this.world.isDay() && !this.world.isClientSide) { + float f = this.aR(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 796fd718f3fe57618aedc9f5a41aac64422e3e34..2197dad881bf5e9d90902c4526db18e19a087230 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -865,6 +865,13 @@ public class CraftWorld implements World { + } + } + ++ // Paper start ++ @Override ++ public boolean isDayTime() { ++ return getHandle().isDay(); ++ } ++ // Paper end ++ + @Override + public long getGameTime() { + return world.worldData.getTime(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index 28a4e90130f51fd2fda7003fde5b4d0a410e1aef..06cbe63ef04e0de824ac0b9d545b6da1f53701b3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -78,4 +78,11 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + public long getSeed() { + return getHandle().lootTableSeed; + } ++ ++ // Paper start ++ @Override ++ public boolean isInDaylight() { ++ return getHandle().isInDaylight(); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0297-Turtle-API.patch b/patches/server-unmapped/0001/0297-Turtle-API.patch new file mode 100644 index 0000000000..5d55bbc3f4 --- /dev/null +++ b/patches/server-unmapped/0001/0297-Turtle-API.patch @@ -0,0 +1,150 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 29 Sep 2018 16:08:23 -0500 +Subject: [PATCH] Turtle API + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalGotoTarget.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalGotoTarget.java +index 62276550627bfe453794a2b3101426fe05a585ff..6a156a488bc073b3b60f4d1081e3f2ab65ba9e96 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalGotoTarget.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalGotoTarget.java +@@ -14,7 +14,7 @@ public abstract class PathfinderGoalGotoTarget extends PathfinderGoal { + protected int c; + protected int d; + private int g; +- protected BlockPosition e; ++ protected BlockPosition e;public final BlockPosition getTargetPosition() { return this.e; } // Paper - OBFHELPER + private boolean h; + private final int i; + private final int j; +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java b/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java +index bf224c97854daa379c61affff6a0ac9524c2c35d..09a6310af6712d36c20167256b60dc3235e76021 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java +@@ -14,6 +14,7 @@ import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.syncher.DataWatcher; + import net.minecraft.network.syncher.DataWatcherObject; + import net.minecraft.network.syncher.DataWatcherRegistry; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.WorldServer; + import net.minecraft.sounds.SoundCategory; +@@ -93,7 +94,7 @@ public class EntityTurtle extends EntityAnimal { + this.datawatcher.set(EntityTurtle.bp, blockposition); + } + +- private BlockPosition getHomePos() { ++ public BlockPosition getHomePos() { // Paper - public + return (BlockPosition) this.datawatcher.get(EntityTurtle.bp); + } + +@@ -109,31 +110,37 @@ public class EntityTurtle extends EntityAnimal { + return (Boolean) this.datawatcher.get(EntityTurtle.bq); + } + +- private void setHasEgg(boolean flag) { ++ public void setHasEgg(boolean flag) { // Paper + this.datawatcher.set(EntityTurtle.bq, flag); + } + ++ public final boolean isDigging() { return this.eL(); } // Paper - OBFHELPER + public boolean eL() { + return (Boolean) this.datawatcher.get(EntityTurtle.br); + } + ++ public final void setDigging(boolean digging) { this.u(digging); } // Paper - OBFHELPER + private void u(boolean flag) { + this.bv = flag ? 1 : 0; + this.datawatcher.set(EntityTurtle.br, flag); + } + ++ public final boolean isGoingHome() { return this.eU(); } // Paper - OBFHELPER + private boolean eU() { + return (Boolean) this.datawatcher.get(EntityTurtle.bt); + } + ++ public final void setGoingHome(boolean goingHome) { this.v(goingHome); } // Paper - OBFHELPER + private void v(boolean flag) { + this.datawatcher.set(EntityTurtle.bt, flag); + } + ++ public final boolean isTravelling() { return this.eV(); } // Paper - OBFHELPER + private boolean eV() { + return (Boolean) this.datawatcher.get(EntityTurtle.bu); + } + ++ public final void setTravelling(boolean travelling) { this.w(travelling); } // Paper - OBFHELPER + private void w(boolean flag) { + this.datawatcher.set(EntityTurtle.bu, flag); + } +@@ -500,14 +507,17 @@ public class EntityTurtle extends EntityAnimal { + + if (!this.g.isInWater() && this.l()) { + if (this.g.bv < 1) { +- this.g.u(true); ++ this.g.setDigging(new com.destroystokyo.paper.event.entity.TurtleStartDiggingEvent((org.bukkit.entity.Turtle) this.g.getBukkitEntity(), MCUtil.toLocation(this.g.world, this.getTargetPosition())).callEvent()); // Paper + } else if (this.g.bv > 200) { + World world = this.g.world; + + // CraftBukkit start +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.g, this.e.up(), (IBlockData) Blocks.TURTLE_EGG.getBlockData().set(BlockTurtleEgg.b, this.g.random.nextInt(4) + 1)).isCancelled()) { ++ // Paper start ++ int eggCount = this.g.random.nextInt(4) + 1; ++ com.destroystokyo.paper.event.entity.TurtleLayEggEvent layEggEvent = new com.destroystokyo.paper.event.entity.TurtleLayEggEvent((org.bukkit.entity.Turtle) this.g.getBukkitEntity(), MCUtil.toLocation(this.g.world, this.e.up()), eggCount); ++ if (layEggEvent.callEvent() && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.g, this.e.up(), Blocks.TURTLE_EGG.getBlockData().set(BlockTurtleEgg.b, layEggEvent.getEggCount())).isCancelled()) { + world.playSound((EntityHuman) null, blockposition, SoundEffects.ENTITY_TURTLE_LAY_EGG, SoundCategory.BLOCKS, 0.3F, 0.9F + world.random.nextFloat() * 0.2F); +- world.setTypeAndData(this.e.up(), (IBlockData) Blocks.TURTLE_EGG.getBlockData().set(BlockTurtleEgg.b, this.g.random.nextInt(4) + 1), 3); ++ world.setTypeAndData(this.e.up(), (IBlockData) Blocks.TURTLE_EGG.getBlockData().set(BlockTurtleEgg.b, layEggEvent.getEggCount()), 3); + } + // CraftBukkit end + this.g.setHasEgg(false); +@@ -636,7 +646,7 @@ public class EntityTurtle extends EntityAnimal { + + @Override + public boolean a() { +- return this.a.isBaby() ? false : (this.a.hasEgg() ? true : (this.a.getRandom().nextInt(700) != 0 ? false : !this.a.getHomePos().a((IPosition) this.a.getPositionVector(), 64.0D))); ++ return this.a.isBaby() ? false : (this.a.hasEgg() ? true : (this.a.getRandom().nextInt(700) != 0 ? false : !this.a.getHomePos().a((IPosition) this.a.getPositionVector(), 64.0D))) && new com.destroystokyo.paper.event.entity.TurtleGoHomeEvent((org.bukkit.entity.Turtle) this.a.getBukkitEntity()).callEvent(); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java +index b46bb75926c14ab54ea309a400eb57405b11ce27..31f1a6b2b9a432cdd25826ced884424eeb50df97 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java +@@ -25,4 +25,36 @@ public class CraftTurtle extends CraftAnimals implements Turtle { + public EntityType getType() { + return EntityType.TURTLE; + } ++ ++ // Paper start ++ @Override ++ public org.bukkit.Location getHome() { ++ return net.minecraft.server.MCUtil.toLocation(getHandle().world, getHandle().getHomePos()); ++ } ++ ++ @Override ++ public void setHome(org.bukkit.Location location) { ++ getHandle().setHomePos(net.minecraft.server.MCUtil.toBlockPosition(location)); ++ } ++ ++ @Override ++ public boolean isGoingHome() { ++ return getHandle().isGoingHome(); ++ } ++ ++ @Override ++ public boolean isDigging() { ++ return getHandle().isDigging(); ++ } ++ ++ @Override ++ public boolean hasEgg() { ++ return getHandle().hasEgg(); ++ } ++ ++ @Override ++ public void setHasEgg(boolean hasEgg) { ++ getHandle().setHasEgg(hasEgg); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0298-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch b/patches/server-unmapped/0001/0298-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch new file mode 100644 index 0000000000..b624461f37 --- /dev/null +++ b/patches/server-unmapped/0001/0298-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 17 Oct 2018 19:17:27 -0400 +Subject: [PATCH] MC-50319: Check other worlds for shooter of projectiles + +Say a player shoots an arrow through a nether portal, the game +would lose the shooter for determining things such as Player Kills, +because the entity is in another world. + +If the projectile fails to find the shooter in the current world, check +other worlds. + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java +index 65cee640040bdd1229149409ff046b765ee08c34..a33f3924a95b86c2337c455f30de9bb257cb8db4 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java +@@ -41,7 +41,18 @@ public abstract class IProjectile extends Entity { + + @Nullable + public Entity getShooter() { +- return this.shooter != null && this.world instanceof WorldServer ? ((WorldServer) this.world).getEntity(this.shooter) : (this.c != 0 ? this.world.getEntity(this.c) : null); ++ // Paper start - MC-50319 - shooter might be in another world (arrows through portals) ++ Entity entity = this.shooter != null && this.world instanceof WorldServer ? ((WorldServer) this.world).getEntity(this.shooter) : (this.c != 0 ? this.world.getEntity(this.c) : null); ++ if (entity == null) { ++ for (WorldServer world : world.getMinecraftServer().getWorlds()) { ++ entity = world.getEntity(this.shooter); ++ if (entity != null) { ++ break; ++ } ++ } ++ } ++ return entity; ++ // Paper end + } + + @Override diff --git a/patches/server-unmapped/0001/0299-Call-player-spectator-target-events-and-improve-impl.patch b/patches/server-unmapped/0001/0299-Call-player-spectator-target-events-and-improve-impl.patch new file mode 100644 index 0000000000..71bb1d21af --- /dev/null +++ b/patches/server-unmapped/0001/0299-Call-player-spectator-target-events-and-improve-impl.patch @@ -0,0 +1,101 @@ +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/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 1271de75743356090050763ff2fb67d28b48cb23..46d1e766e234bf49d31583e9e59aeb33c719b1ec 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -1811,15 +1811,59 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + return (Entity) (this.spectatedEntity == null ? this : this.spectatedEntity); + } + +- public void setSpectatorTarget(Entity entity) { ++ public void setSpectatorTarget(Entity newSpectatorTarget) { ++ // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity Event and improve implementation + Entity entity1 = this.getSpecatorTarget(); + +- this.spectatedEntity = (Entity) (entity == null ? this : entity); +- if (entity1 != this.spectatedEntity) { +- this.playerConnection.sendPacket(new PacketPlayOutCamera(this.spectatedEntity)); +- this.playerConnection.a(this.spectatedEntity.locX(), this.spectatedEntity.locY(), this.spectatedEntity.locZ(), this.yaw, this.pitch, TeleportCause.SPECTATE); // CraftBukkit ++ if (newSpectatorTarget == null) { ++ newSpectatorTarget = this; + } + ++ if (entity1 == newSpectatorTarget) return; // new spec target is the current spec target ++ ++ if (newSpectatorTarget == this) { ++ com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity()); ++ ++ if (!playerStopSpectatingEntityEvent.callEvent()) { ++ return; ++ } ++ } else { ++ com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent playerStartSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity(), newSpectatorTarget.getBukkitEntity()); ++ ++ if (!playerStartSpectatingEntityEvent.callEvent()) { ++ return; ++ } ++ } ++ // Validate ++ if (newSpectatorTarget != this) { ++ if (newSpectatorTarget.dead || newSpectatorTarget.shouldBeRemoved || !newSpectatorTarget.valid || newSpectatorTarget.world == null) { ++ MinecraftServer.LOGGER.info("Blocking player " + this.toString() + " from spectating invalid entity " + newSpectatorTarget.toString()); ++ return; ++ } ++ if (this.isFrozen()) { ++ // use debug: clients might maliciously spam this ++ MinecraftServer.LOGGER.debug("Blocking frozen player " + this.toString() + " from spectating entity " + newSpectatorTarget.toString()); ++ return; ++ } ++ } ++ ++ this.spectatedEntity = newSpectatorTarget; // only set after validating state ++ ++ if (newSpectatorTarget != this) { ++ // Make sure we're in the right place ++ this.ejectPassengers(); // teleport can fail if we have passengers... ++ this.getBukkitEntity().teleport(new Location(newSpectatorTarget.getWorld().getWorld(), newSpectatorTarget.locX(), newSpectatorTarget.locY(), newSpectatorTarget.locZ(), this.yaw, this.pitch), TeleportCause.SPECTATE); // Correctly handle cross-world entities from api calls by using CB teleport ++ ++ // Make sure we're tracking the entity before sending ++ PlayerChunkMap.EntityTracker tracker = ((WorldServer)newSpectatorTarget.world).getChunkProvider().playerChunkMap.trackedEntities.get(newSpectatorTarget.getId()); ++ if (tracker != null) { // dumb plugins... ++ tracker.updatePlayer(this); ++ } ++ } else { ++ this.playerConnection.teleport(this.spectatedEntity.locX(), this.spectatedEntity.locY(), this.spectatedEntity.locZ(), this.yaw, this.pitch, TeleportCause.SPECTATE); // CraftBukkit ++ } ++ this.playerConnection.sendPacket(new PacketPlayOutCamera(newSpectatorTarget)); ++ // Paper end + } + + @Override +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 74942bf7641eafab85202fd6a521780ce4cc1f65..5ea1354fb9d1c570603fc6814198639a741a9e34 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1355,6 +1355,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + + // CraftBukkit start - Delegate to teleport(Location) ++ public final void teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) { this.a(d0, d1, d2, f, f1, cause); } // Paper - OBFHELPER + public void a(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) { + this.a(d0, d1, d2, f, f1, Collections.emptySet(), cause); + } diff --git a/patches/server-unmapped/0001/0300-Add-Velocity-IP-Forwarding-Support.patch b/patches/server-unmapped/0001/0300-Add-Velocity-IP-Forwarding-Support.patch new file mode 100644 index 0000000000..4c21b54f18 --- /dev/null +++ b/patches/server-unmapped/0001/0300-Add-Velocity-IP-Forwarding-Support.patch @@ -0,0 +1,302 @@ +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/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 7178b37f7978c7e9031a22726005c5099fd78fe0..3139c194f9b1bc3510d51a81f13ae43d00a3dc29 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -8,6 +8,7 @@ import java.io.IOException; + import java.lang.reflect.InvocationTargetException; + import java.lang.reflect.Method; + import java.lang.reflect.Modifier; ++import java.nio.charset.StandardCharsets; + import java.util.HashMap; + import java.util.List; + import java.util.Map; +@@ -252,7 +253,7 @@ public class PaperConfig { + } + + public static boolean isProxyOnlineMode() { +- return Bukkit.getOnlineMode() || (SpigotConfig.bungee && bungeeOnlineMode); ++ return Bukkit.getOnlineMode() || (SpigotConfig.bungee && bungeeOnlineMode) || (velocitySupport && velocityOnlineMode); + } + + public static int packetInSpamThreshold = 300; +@@ -324,4 +325,18 @@ public class PaperConfig { + } + tabSpamLimit = getInt("settings.spam-limiter.tab-spam-limit", tabSpamLimit); + } ++ ++ public static boolean velocitySupport; ++ public static boolean velocityOnlineMode; ++ public static byte[] velocitySecretKey; ++ private static void velocitySupport() { ++ velocitySupport = getBoolean("settings.velocity-support.enabled", false); ++ velocityOnlineMode = getBoolean("settings.velocity-support.online-mode", false); ++ String secret = getString("settings.velocity-support.secret", ""); ++ if (velocitySupport && secret.isEmpty()) { ++ fatal("Velocity support is enabled, but no secret key was specified. A secret key is required!"); ++ } else { ++ velocitySecretKey = secret.getBytes(StandardCharsets.UTF_8); ++ } ++ } + } +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..e6afaa41df086b1eb3950ce870c91dd5bf5a663b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java +@@ -0,0 +1,67 @@ ++package com.destroystokyo.paper.proxy; ++ ++import com.destroystokyo.paper.PaperConfig; ++import com.google.common.net.InetAddresses; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.resources.MinecraftKey; ++ ++import java.net.InetAddress; ++import java.security.InvalidKeyException; ++import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++ ++import javax.crypto.Mac; ++import javax.crypto.spec.SecretKeySpec; ++ ++public class VelocityProxy { ++ private static final int SUPPORTED_FORWARDING_VERSION = 1; ++ public static final MinecraftKey PLAYER_INFO_CHANNEL = new MinecraftKey("velocity", "player_info"); ++ ++ public static boolean checkIntegrity(final PacketDataSerializer 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(PaperConfig.velocitySecretKey, "HmacSHA256")); ++ final byte[] mySignature = mac.doFinal(data); ++ if (!MessageDigest.isEqual(signature, mySignature)) { ++ return false; ++ } ++ } catch (final InvalidKeyException | NoSuchAlgorithmException e) { ++ throw new AssertionError(e); ++ } ++ ++ int version = buf.readVarInt(); ++ if (version != SUPPORTED_FORWARDING_VERSION) { ++ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + SUPPORTED_FORWARDING_VERSION); ++ } ++ ++ return true; ++ } ++ ++ public static InetAddress readAddress(final PacketDataSerializer buf) { ++ return InetAddresses.forString(buf.readUTF(Short.MAX_VALUE)); ++ } ++ ++ public static GameProfile createProfile(final PacketDataSerializer buf) { ++ final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUTF(16)); ++ readProperties(buf, profile); ++ return profile; ++ } ++ ++ private static void readProperties(final PacketDataSerializer 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)); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/network/PacketDataSerializer.java b/src/main/java/net/minecraft/network/PacketDataSerializer.java +index 5a1187b001004afe22d208bc5d7c288e796e16a6..579eb1260c7266cd41025cff177de4fb00ac0cec 100644 +--- a/src/main/java/net/minecraft/network/PacketDataSerializer.java ++++ b/src/main/java/net/minecraft/network/PacketDataSerializer.java +@@ -192,6 +192,7 @@ public class PacketDataSerializer extends ByteBuf { + return this.d(oenum.ordinal()); + } + ++ public int readVarInt() { return i(); } // Paper - OBFHELPER + public int i() { + int i = 0; + int j = 0; +@@ -232,6 +233,7 @@ public class PacketDataSerializer extends ByteBuf { + return this; + } + ++ public UUID readUUID() { return k(); } // Paper - OBFHELPER + public UUID k() { + return new UUID(this.readLong(), this.readLong()); + } +@@ -359,6 +361,7 @@ public class PacketDataSerializer extends ByteBuf { + } + } + ++ public String readUTF(int maxLength) { return this.e(maxLength); } // Paper - OBFHELPER + public String e(int i) { + int j = this.i(); + +diff --git a/src/main/java/net/minecraft/network/protocol/login/PacketLoginInCustomPayload.java b/src/main/java/net/minecraft/network/protocol/login/PacketLoginInCustomPayload.java +index c1bac2d07e5107c1346f246f5d5d929c73912bfd..c47c2d774a1990ad5007bbdc7bd3a81b3f3817e3 100644 +--- a/src/main/java/net/minecraft/network/protocol/login/PacketLoginInCustomPayload.java ++++ b/src/main/java/net/minecraft/network/protocol/login/PacketLoginInCustomPayload.java +@@ -6,8 +6,8 @@ import net.minecraft.network.protocol.Packet; + + public class PacketLoginInCustomPayload implements Packet { + +- private int a; +- private PacketDataSerializer b; ++ private int a; public int getId() { return a; } // Paper - OBFHELPER ++ private PacketDataSerializer b; public PacketDataSerializer getBuf() { return b; } // Paper - OBFHELPER + + public PacketLoginInCustomPayload() {} + +diff --git a/src/main/java/net/minecraft/network/protocol/login/PacketLoginOutCustomPayload.java b/src/main/java/net/minecraft/network/protocol/login/PacketLoginOutCustomPayload.java +index eb970c1e954cb0aa83aa12e83c471778809e69b2..2d8c917509f10a96fc82404908b452cb385c7c60 100644 +--- a/src/main/java/net/minecraft/network/protocol/login/PacketLoginOutCustomPayload.java ++++ b/src/main/java/net/minecraft/network/protocol/login/PacketLoginOutCustomPayload.java +@@ -13,6 +13,14 @@ public class PacketLoginOutCustomPayload implements Packet { ++ try { ++ new LoginHandler().fireEvents(); ++ } catch (Exception ex) { ++ disconnect("Failed to verify username!"); ++ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + i.getName(), ex); ++ } ++ }); ++ return; ++ } ++ // Paper end + this.disconnect(new ChatMessage("multiplayer.disconnect.unexpected_query_response")); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 85975c5a1acca101d182dfd01879161e34bfe5c3..ed541a9782d09d3537e04e3651833208a83d7b12 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -685,7 +685,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 || com.destroystokyo.paper.PaperConfig.velocitySupport) { // Paper - Velocity support + return -1; + } else { + return this.configuration.getInt("settings.connection-throttle"); diff --git a/patches/server-unmapped/0001/0301-Add-more-Witch-API.patch b/patches/server-unmapped/0001/0301-Add-more-Witch-API.patch new file mode 100644 index 0000000000..0f0e488ada --- /dev/null +++ b/patches/server-unmapped/0001/0301-Add-more-Witch-API.patch @@ -0,0 +1,148 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 12 Oct 2018 14:10:46 -0500 +Subject: [PATCH] Add more Witch API + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java b/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java +index c6d79125e7dd982fc528ce61144005194cbaa323..63fb08e7b4290353e5148d1acb58f091dc5b08be 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityWitch.java +@@ -1,5 +1,11 @@ + package net.minecraft.world.entity.monster; + ++// Paper start ++import com.destroystokyo.paper.event.entity.WitchReadyPotionEvent; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.entity.Witch; ++// Paper end ++ + import java.util.Iterator; + import java.util.List; + import java.util.UUID; +@@ -49,7 +55,7 @@ public class EntityWitch extends EntityRaider implements IRangedEntity { + private static final UUID b = UUID.fromString("5CD17E52-A79A-43D3-A529-90FDE04B181E"); + private static final AttributeModifier bo = new AttributeModifier(EntityWitch.b, "Drinking speed penalty", -0.25D, AttributeModifier.Operation.ADDITION); + private static final DataWatcherObject bp = DataWatcher.a(EntityWitch.class, DataWatcherRegistry.i); +- private int bq; ++ private int bq; public int getPotionUseTimeLeft() { return bq; } public void setPotionUseTimeLeft(int timeLeft) { bq = timeLeft; } // Paper - OBFHELPER + private PathfinderGoalNearestHealableRaider br; + private PathfinderGoalNearestAttackableTargetWitch bs; + +@@ -95,10 +101,12 @@ public class EntityWitch extends EntityRaider implements IRangedEntity { + return SoundEffects.ENTITY_WITCH_DEATH; + } + ++ public void setDrinkingPotion(boolean drinkingPotion) { v(drinkingPotion); } // Paper - OBFHELPER + public void v(boolean flag) { + this.getDataWatcher().set(EntityWitch.bp, flag); + } + ++ public boolean isDrinkingPotion() { return m(); } // Paper - OBFHELPER + public boolean m() { + return (Boolean) this.getDataWatcher().get(EntityWitch.bp); + } +@@ -157,21 +165,24 @@ public class EntityWitch extends EntityRaider implements IRangedEntity { + } + + if (potionregistry != null) { +- // Paper start + ItemStack potion = PotionUtil.a(new ItemStack(Items.POTION), potionregistry); +- org.bukkit.inventory.ItemStack bukkitStack = com.destroystokyo.paper.event.entity.WitchReadyPotionEvent.process((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potion)); +- this.setSlot(EnumItemSlot.MAINHAND, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(bukkitStack)); ++ // Paper start - logic moved into setDrinkingPotion, copy exact impl into the method and then comment out ++ this.setDrinkingPotion(potion); ++// org.bukkit.inventory.ItemStack bukkitStack = com.destroystokyo.paper.event.entity.WitchReadyPotionEvent.process((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potion)); ++// this.setSlot(EnumItemSlot.MAINHAND, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(bukkitStack)); ++// // Paper end ++// this.bq = this.getItemInMainHand().k(); ++// this.v(true); ++// if (!this.isSilent()) { ++// this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_WITCH_DRINK, this.getSoundCategory(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); ++// } ++// ++// AttributeModifiable attributemodifiable = this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED); ++// ++// attributemodifiable.removeModifier(EntityWitch.bo); ++// attributemodifiable.b(EntityWitch.bo); + // Paper end +- this.bq = this.getItemInMainHand().k(); +- this.v(true); +- if (!this.isSilent()) { +- this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_WITCH_DRINK, this.getSoundCategory(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); +- } + +- AttributeModifiable attributemodifiable = this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED); +- +- attributemodifiable.removeModifier(EntityWitch.bo); +- attributemodifiable.b(EntityWitch.bo); + } + } + +@@ -183,6 +194,24 @@ public class EntityWitch extends EntityRaider implements IRangedEntity { + super.movementTick(); + } + ++ // Paper start - moved to its own method ++ public void setDrinkingPotion(ItemStack potion) { ++ org.bukkit.inventory.ItemStack bukkitStack = com.destroystokyo.paper.event.entity.WitchReadyPotionEvent.process((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potion)); ++ this.setSlot(EnumItemSlot.MAINHAND, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(bukkitStack)); ++ // Paper end ++ this.bq = this.getItemInMainHand().k(); ++ this.v(true); ++ if (!this.isSilent()) { ++ this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_WITCH_DRINK, this.getSoundCategory(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); ++ } ++ ++ AttributeModifiable attributemodifiable = this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED); ++ ++ attributemodifiable.removeModifier(EntityWitch.bo); ++ attributemodifiable.b(EntityWitch.bo); ++ } ++ // Paper end ++ + @Override + public SoundEffect eL() { + return SoundEffects.ENTITY_WITCH_CELEBRATE; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +index 9cc34cdb43596eff34625045f884b93da3f27ab6..72428396a8c91d5d848b3019aaaa03bc3283574b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +@@ -4,6 +4,13 @@ import net.minecraft.world.entity.monster.EntityWitch; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.Witch; ++// Paper start ++import com.destroystokyo.paper.entity.CraftRangedEntity; ++import com.google.common.base.Preconditions; ++import org.bukkit.Material; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.inventory.ItemStack; ++// Paper end + + public class CraftWitch extends CraftRaider implements Witch, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + public CraftWitch(CraftServer server, EntityWitch entity) { +@@ -24,4 +31,23 @@ public class CraftWitch extends CraftRaider implements Witch, com.destroystokyo. + public EntityType getType() { + return EntityType.WITCH; + } ++ ++ // Paper start ++ public boolean isDrinkingPotion() { ++ return getHandle().isDrinkingPotion(); ++ } ++ ++ public int getPotionUseTimeLeft() { ++ return getHandle().getPotionUseTimeLeft(); ++ } ++ ++ public ItemStack getDrinkingPotion() { ++ return CraftItemStack.asCraftMirror(getHandle().getItemInMainHand()); ++ } ++ ++ public void setDrinkingPotion(ItemStack potion) { ++ Preconditions.checkArgument(potion == null || potion.getType().isEmpty() || potion.getType() == Material.POTION, "must be potion, air, or null"); ++ getHandle().setDrinkingPotion(CraftItemStack.asNMSCopy(potion)); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0302-Check-Drowned-for-Villager-Aggression-Config.patch b/patches/server-unmapped/0001/0302-Check-Drowned-for-Villager-Aggression-Config.patch new file mode 100644 index 0000000000..efb266e1a5 --- /dev/null +++ b/patches/server-unmapped/0001/0302-Check-Drowned-for-Villager-Aggression-Config.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Wed, 10 Oct 2018 21:22:44 -0500 +Subject: [PATCH] Check Drowned for Villager Aggression Config + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityDrowned.java b/src/main/java/net/minecraft/world/entity/monster/EntityDrowned.java +index c3457fc7c0f72af79b12dc50c270ca24b7590c22..e4794760fc918cccbdc3f8d10ab21dd9b6f29e8e 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityDrowned.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityDrowned.java +@@ -82,7 +82,7 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity { + this.goalSelector.a(7, new PathfinderGoalRandomStroll(this, 1.0D)); + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityDrowned.class})).a(EntityPigZombie.class)); + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, this::i)); +- this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)); ++ if ( world.spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)); // Paper + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, true)); + this.targetSelector.a(5, new PathfinderGoalNearestAttackableTarget<>(this, EntityTurtle.class, 10, true, false, EntityTurtle.bo)); + } diff --git a/patches/server-unmapped/0001/0303-Here-s-Johnny.patch b/patches/server-unmapped/0001/0303-Here-s-Johnny.patch new file mode 100644 index 0000000000..b9a50189c2 --- /dev/null +++ b/patches/server-unmapped/0001/0303-Here-s-Johnny.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 12 Oct 2018 01:37:22 -0500 +Subject: [PATCH] Here's Johnny! + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java b/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java +index c181d5f5e6108ade54fc97c665897d1db5e90719..c45dcb56af95f3e87e292b92b697a336461f01bc 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java +@@ -51,7 +51,7 @@ public class EntityVindicator extends EntityIllagerAbstract { + private static final Predicate b = (enumdifficulty) -> { + return enumdifficulty == EnumDifficulty.NORMAL || enumdifficulty == EnumDifficulty.HARD; + }; +- private boolean bo; ++ private boolean bo; public boolean isJohnny() { return bo; } public void setJohnny(boolean johnny) { bo = johnny; } // Paper - OBFHELPER + + public EntityVindicator(EntityTypes entitytypes, World world) { + super(entitytypes, world); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java +index e793820e1ede4bd4d31e6fe12ca8189707674ffe..b128e8291f2d7652a98f1271d763e33afa5de9f7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java +@@ -25,4 +25,14 @@ public class CraftVindicator extends CraftIllager implements Vindicator { + public EntityType getType() { + return EntityType.VINDICATOR; + } ++ ++ // Paper start ++ public boolean isJohnny() { ++ return getHandle().isJohnny(); ++ } ++ ++ public void setJohnny(boolean johnny) { ++ getHandle().setJohnny(johnny); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0304-Add-option-to-prevent-players-from-moving-into-unloa.patch b/patches/server-unmapped/0001/0304-Add-option-to-prevent-players-from-moving-into-unloa.patch new file mode 100644 index 0000000000..7bf30d725c --- /dev/null +++ b/patches/server-unmapped/0001/0304-Add-option-to-prevent-players-from-moving-into-unloa.patch @@ -0,0 +1,64 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index f280dbff4a09bc611a9ca565c6d697d08801f53b..fbf3ccfb347a5ba6e895339e9576629d940d1aa4 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -396,4 +396,9 @@ public class PaperWorldConfig { + waterOverLavaFlowSpeed = getInt("water-over-lava-flow-speed", 5); + log("Water over lava flow speed: " + waterOverLavaFlowSpeed); + } ++ ++ public boolean preventMovingIntoUnloadedChunks = false; ++ private void preventMovingIntoUnloadedChunks() { ++ preventMovingIntoUnloadedChunks = getBoolean("prevent-moving-into-unloaded-chunks", false); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 5ea1354fb9d1c570603fc6814198639a741a9e34..69b27924b8f12b647133fedcfb0568698d19f413 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -543,6 +543,13 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + speed *= 2f; // TODO: Get the speed of the vehicle instead of the player + ++ // Paper start - Prevent moving into unloaded chunks ++ if (player.world.paperConfig.preventMovingIntoUnloadedChunks && worldserver.getChunkIfLoadedImmediately((int) Math.floor(packetplayinvehiclemove.getX()) >> 4, (int) Math.floor(packetplayinvehiclemove.getZ()) >> 4) == null) { ++ this.networkManager.sendPacket(new PacketPlayOutVehicleMove(entity)); ++ return; ++ } ++ // Paper end ++ + if (d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isExemptPlayer()) { + // CraftBukkit end + PlayerConnection.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", entity.getDisplayName().getString(), this.player.getDisplayName().getString(), d6, d7, d8); +@@ -1141,9 +1148,9 @@ public class PlayerConnection implements PacketListenerPlayIn { + double d1 = this.player.locY(); + double d2 = this.player.locZ(); + double d3 = this.player.locY(); +- double d4 = packetplayinflying.a(this.player.locX()); ++ double d4 = packetplayinflying.a(this.player.locX());double toX = d4; // Paper - OBFHELPER + double d5 = packetplayinflying.b(this.player.locY()); +- double d6 = packetplayinflying.c(this.player.locZ()); ++ double d6 = packetplayinflying.c(this.player.locZ());double toZ = d6; // Paper - OBFHELPER + float f = packetplayinflying.a(this.player.yaw); + float f1 = packetplayinflying.b(this.player.pitch); + double d7 = d4 - this.l; +@@ -1182,6 +1189,12 @@ public class PlayerConnection implements PacketListenerPlayIn { + } else { + speed = player.abilities.walkSpeed * 10f; + } ++ // Paper start - Prevent moving into unloaded chunks ++ if (player.world.paperConfig.preventMovingIntoUnloadedChunks && (this.player.locX() != toX || this.player.locZ() != toZ) && !worldserver.isChunkLoaded((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4)) { ++ this.internalTeleport(this.player.locX(), this.player.locY(), this.player.locZ(), this.player.yaw, this.player.pitch, Collections.emptySet()); ++ return; ++ } ++ // Paper end + + if (!this.player.H() && (!this.player.getWorldServer().getGameRules().getBoolean(GameRules.DISABLE_ELYTRA_MOVEMENT_CHECK) || !this.player.isGliding())) { + float f2 = this.player.isGliding() ? 300.0F : 100.0F; diff --git a/patches/server-unmapped/0001/0305-Reset-players-airTicks-on-respawn.patch b/patches/server-unmapped/0001/0305-Reset-players-airTicks-on-respawn.patch new file mode 100644 index 0000000000..9c936ab726 --- /dev/null +++ b/patches/server-unmapped/0001/0305-Reset-players-airTicks-on-respawn.patch @@ -0,0 +1,30 @@ +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/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 46d1e766e234bf49d31583e9e59aeb33c719b1ec..fbc0c81bb7e87f7820325a9a7bb39123db272845 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -2153,6 +2153,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + + this.setHealth(this.getMaxHealth()); ++ this.setAirTicks(this.getMaxAirTicks()); // Paper + this.fireTicks = 0; + this.fallDistance = 0; + this.foodData = new FoodMetaData(this); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index b6b4eb9ac883cfdfab5f114767fb5cfb29445730..0b61d03506bd56cf7e373daacbf4fb2e29bb0a58 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2350,6 +2350,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + } + ++ public final int getMaxAirTicks() { return bH(); } // Paper - OBFHELPER + public int bH() { + return 300; + } diff --git a/patches/server-unmapped/0001/0306-Don-t-sleep-after-profile-lookups-if-not-needed.patch b/patches/server-unmapped/0001/0306-Don-t-sleep-after-profile-lookups-if-not-needed.patch new file mode 100644 index 0000000000..01be147e24 --- /dev/null +++ b/patches/server-unmapped/0001/0306-Don-t-sleep-after-profile-lookups-if-not-needed.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 23 Oct 2018 20:25:05 -0400 +Subject: [PATCH] Don't sleep after profile lookups if not needed + +Mojang was sleeping even if we had no more requests to go after +the current one finished, resulting in 100ms lost per profile lookup + +diff --git a/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java b/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java +index a3ab666b5fa89aad7ee167d9aeff2f62019a4a78..8e182fdd69dba6e1c52e2f6a893534d77fb3bfaa 100644 +--- a/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java ++++ b/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java +@@ -43,6 +43,7 @@ public class YggdrasilGameProfileRepository implements GameProfileRepository { + } + + final int page = 0; ++ boolean hasRequested = false; // Paper + + for (final List request : Iterables.partition(criteria, ENTRIES_PER_PAGE)) { + int failCount = 0; +@@ -68,6 +69,12 @@ public class YggdrasilGameProfileRepository implements GameProfileRepository { + LOGGER.debug("Couldn't find profile {}", name); + callback.onProfileLookupFailed(new GameProfile(null, name), new ProfileNotFoundException("Server did not find the requested profile")); + } ++ // Paper start ++ if (!hasRequested) { ++ hasRequested = true; ++ continue; ++ } ++ // Paper end + + try { + Thread.sleep(DELAY_BETWEEN_PAGES); diff --git a/patches/server-unmapped/0001/0307-Improve-Server-Thread-Pool-and-Thread-Priorities.patch b/patches/server-unmapped/0001/0307-Improve-Server-Thread-Pool-and-Thread-Priorities.patch new file mode 100644 index 0000000000..ed9595b88b --- /dev/null +++ b/patches/server-unmapped/0001/0307-Improve-Server-Thread-Pool-and-Thread-Priorities.patch @@ -0,0 +1,108 @@ +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. + +diff --git a/src/main/java/net/minecraft/SystemUtils.java b/src/main/java/net/minecraft/SystemUtils.java +index 68ce7605bd63ea280b96db8230463d2afb0a6cb1..46d82c1548088b8305f758699388edf0d5d4d050 100644 +--- a/src/main/java/net/minecraft/SystemUtils.java ++++ b/src/main/java/net/minecraft/SystemUtils.java +@@ -45,6 +45,7 @@ import java.util.stream.Stream; + import javax.annotation.Nullable; + import net.minecraft.resources.MinecraftKey; + import net.minecraft.server.DispenserRegistry; ++import net.minecraft.server.ServerWorkerThread; + import net.minecraft.util.MathHelper; + import net.minecraft.util.datafix.DataConverterRegistry; + import net.minecraft.world.level.block.state.properties.IBlockState; +@@ -54,8 +55,8 @@ import org.apache.logging.log4j.Logger; + public class SystemUtils { + + private static final AtomicInteger c = new AtomicInteger(1); +- private static final ExecutorService d = a("Bootstrap"); +- private static final ExecutorService e = a("Main"); ++ private static final ExecutorService d = a("Bootstrap", -2); // Paper - add -2 priority ++ private static final ExecutorService e = a("Main", -1); // Paper - add -1 priority + private static final ExecutorService f = n(); + public static LongSupplier a = System::nanoTime; + public static final UUID b = new UUID(0L, 0L); public static final UUID getNullUUID() {return b;} // Paper OBFHELPER +@@ -85,15 +86,18 @@ public class SystemUtils { + return Instant.now().toEpochMilli(); + } + +- private static ExecutorService a(String s) { +- int i = MathHelper.clamp(Runtime.getRuntime().availableProcessors() - 1, 1, 7); +- Object object; ++ private static ExecutorService a(String s, int priorityModifier) { // Paper - add priority ++ // Paper start - use simpler thread pool that allows 1 thread ++ int i = Math.min(8, Math.max(Runtime.getRuntime().availableProcessors() - 2, 1)); ++ i = Integer.getInteger("Paper.WorkerThreadCount", i); ++ ExecutorService object; + + if (i <= 0) { + object = MoreExecutors.newDirectExecutorService(); + } else { +- object = new ForkJoinPool(i, (forkjoinpool) -> { +- ForkJoinWorkerThread forkjoinworkerthread = new ForkJoinWorkerThread(forkjoinpool) { ++ object = new java.util.concurrent.ThreadPoolExecutor(i, i,0L, TimeUnit.MILLISECONDS, new java.util.concurrent.LinkedBlockingQueue(), target -> new ServerWorkerThread(target, s, priorityModifier)); ++ } ++ /* + protected void onTermination(Throwable throwable) { + if (throwable != null) { + SystemUtils.LOGGER.warn("{} died", this.getName(), throwable); +@@ -109,6 +113,7 @@ public class SystemUtils { + return forkjoinworkerthread; + }, SystemUtils::a, true); + } ++ }*/ // Paper end + + return (ExecutorService) object; + } +@@ -157,6 +162,7 @@ public class SystemUtils { + }); + } + ++ public static void onThreadError(Thread thread, Throwable throwable) { a(thread, throwable); } // Paper - OBFHELPER + private static void a(Thread thread, Throwable throwable) { + c(throwable); + if (throwable instanceof CompletionException) { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 1733717b7cae3e7a805e5275ff89967744c4bc4a..4cb57914db19d6024501f2b75361c3d8acdd4149 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -285,6 +285,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +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 4cb57914db19d6024501f2b75361c3d8acdd4149..7dee17e52a5b23ba4d8089482b39215041a64579 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1315,12 +1315,24 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Mon, 5 Nov 2018 04:23:51 +0000 +Subject: [PATCH] Restore custom InventoryHolder support + +Upstream removed the ability to consistently use a custom InventoryHolder, +However, the implementation does not use an InventoryHolder in any form +outside of custom inventories. + +We can take that knowledge and apply some expected behavior, if we're given +an inventory holder, we should use it and return a custom inventory with the +holder, otherwise, create an inventory backed by the intended inventory, as +per upstream behavior. + +This provides a "best of both worlds" scenario: plugins with InventoryHolder's +will always work as intended in the past, those without will create implementation +based inventories. + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java +index 94d807c5d09f165c6eedd0a1c4026c2b833806a0..3e56de295be0d03dddd3e54fcd7b05d4b9c74dc4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java +@@ -40,6 +40,11 @@ public final class CraftInventoryCreator { + } + + public Inventory createInventory(InventoryHolder holder, InventoryType type) { ++ // Paper start ++ if (holder != null) { ++ return DEFAULT_CONVERTER.createInventory(holder, type); ++ } ++ //noinspection ConstantConditions // Paper end + return converterMap.get(type).createInventory(holder, type); + } + +@@ -55,6 +60,11 @@ public final class CraftInventoryCreator { + // Paper end + + public Inventory createInventory(InventoryHolder holder, InventoryType type, String title) { ++ // Paper start ++ if (holder != null) { ++ return DEFAULT_CONVERTER.createInventory(holder, type, title); ++ } ++ //noinspection ConstantConditions // Paper end + return converterMap.get(type).createInventory(holder, type, title); + } + diff --git a/patches/server-unmapped/0001/0310-Use-Vanilla-Minecart-Speeds.patch b/patches/server-unmapped/0001/0310-Use-Vanilla-Minecart-Speeds.patch new file mode 100644 index 0000000000..7e7e806b36 --- /dev/null +++ b/patches/server-unmapped/0001/0310-Use-Vanilla-Minecart-Speeds.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 8 Nov 2018 21:33:09 -0500 +Subject: [PATCH] Use Vanilla Minecart Speeds + +CraftBukkit changed the values on flying speed, restore back to vanilla + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java +index 1f94cc096d95129d85a6278b1e369729df93d27d..7d91e6b75a8a827853b0ca8e53b8ec19e2cf1092 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java +@@ -100,9 +100,9 @@ public abstract class EntityMinecartAbstract extends Entity { + private double derailedX = 0.5; + private double derailedY = 0.5; + private double derailedZ = 0.5; +- private double flyingX = 0.95; +- private double flyingY = 0.95; +- private double flyingZ = 0.95; ++ private double flyingX = 0.949999988079071D; // Paper - restore vanilla precision ++ private double flyingY = 0.949999988079071D; // Paper - restore vanilla precision ++ private double flyingZ = 0.949999988079071D; // Paper - restore vanilla precision + public double maxSpeed = 0.4D; + // CraftBukkit end + diff --git a/patches/server-unmapped/0001/0311-Fix-SpongeAbsortEvent-handling.patch b/patches/server-unmapped/0001/0311-Fix-SpongeAbsortEvent-handling.patch new file mode 100644 index 0000000000..f0c682b058 --- /dev/null +++ b/patches/server-unmapped/0001/0311-Fix-SpongeAbsortEvent-handling.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 10 Nov 2018 05:15:21 +0000 +Subject: [PATCH] Fix SpongeAbsortEvent handling + +Only process drops when the block is actually going to be removed + +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 615a4418fd276cd3e0b3686d962ebaf13ef5d4be..e5c43b383a93fac76333a67b41535ab009d1dcf3 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -228,6 +228,7 @@ public class Block extends BlockBase implements IMaterial { + + } + ++ public static void dropNaturally(IBlockData iblockdata, GeneratorAccess generatoraccess, BlockPosition blockposition, @Nullable TileEntity tileentity) { a(iblockdata, generatoraccess, blockposition, tileentity); } + public static void a(IBlockData iblockdata, GeneratorAccess generatoraccess, BlockPosition blockposition, @Nullable TileEntity tileentity) { + if (generatoraccess instanceof WorldServer) { + a(iblockdata, (WorldServer) generatoraccess, blockposition, tileentity).forEach((itemstack) -> { +diff --git a/src/main/java/net/minecraft/world/level/block/BlockSponge.java b/src/main/java/net/minecraft/world/level/block/BlockSponge.java +index ef48ad0ab68e2e050bc8aca484d850297990b78e..d80eee47390ab202eea0368571421bbc94655ab1 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockSponge.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockSponge.java +@@ -129,8 +129,11 @@ public class BlockSponge extends Block { + // NOP + } else if (material == Material.WATER_PLANT || material == Material.REPLACEABLE_WATER_PLANT) { + TileEntity tileentity = iblockdata.getBlock().isTileEntity() ? world.getTileEntity(blockposition2) : null; +- +- a(iblockdata, world, blockposition2, tileentity); ++ // Paper start ++ if (block.getHandle().getMaterial() == Material.AIR) { ++ dropNaturally(iblockdata, world, blockposition2, tileentity); ++ } ++ // Paper end + } + } + world.setTypeAndData(blockposition2, block.getHandle(), block.getFlag()); diff --git a/patches/server-unmapped/0001/0312-Don-t-allow-digging-into-unloaded-chunks.patch b/patches/server-unmapped/0001/0312-Don-t-allow-digging-into-unloaded-chunks.patch new file mode 100644 index 0000000000..797bc5af1c --- /dev/null +++ b/patches/server-unmapped/0001/0312-Don-t-allow-digging-into-unloaded-chunks.patch @@ -0,0 +1,68 @@ +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/PlayerInteractManager.java b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +index 0ead8f1fabcc8debea8e2211d58a83b34acfcf0b..2892189bbacb3307cfa7c2b3cbb9cd56eda9ef3c 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerInteractManager.java ++++ b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +@@ -113,8 +113,8 @@ public class PlayerInteractManager { + IBlockData iblockdata; + + if (this.j) { +- iblockdata = this.world.getType(this.k); +- if (iblockdata.isAir()) { ++ iblockdata = this.world.getTypeIfLoaded(this.k); // Paper ++ if (iblockdata == null || iblockdata.isAir()) { // Paper + this.j = false; + } else { + float f = this.a(iblockdata, this.k, this.l); +@@ -125,7 +125,13 @@ public class PlayerInteractManager { + } + } + } else if (this.f) { +- iblockdata = this.world.getType(this.h); ++ // Paper start - don't want to do same logic as above, return instead ++ iblockdata = this.world.getTypeIfLoaded(this.h); ++ if (iblockdata == null) { ++ this.f = false; ++ return; ++ } ++ // Paper end + if (iblockdata.isAir()) { + this.world.a(this.player.getId(), this.h, -1); + this.m = -1; +@@ -289,10 +295,12 @@ public class PlayerInteractManager { + this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "stopped destroying")); + } else if (packetplayinblockdig_enumplayerdigtype == PacketPlayInBlockDig.EnumPlayerDigType.ABORT_DESTROY_BLOCK) { + this.f = false; +- if (!Objects.equals(this.h, blockposition)) { ++ if (!Objects.equals(this.h, blockposition) && !BlockPosition.ZERO.equals(this.h)) { + PlayerInteractManager.LOGGER.debug("Mismatch in destroy block pos: " + this.h + " " + blockposition); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled +- this.world.a(this.player.getId(), this.h, -1); +- this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(this.h, this.world.getType(this.h), packetplayinblockdig_enumplayerdigtype, true, "aborted mismatched destroying")); ++ IBlockData type = this.world.getTypeIfLoaded(this.h); // Paper - don't load unloaded chunks for stale records here ++ if (type != null) this.world.a(this.player.getId(), this.h, -1); // Paper ++ if (type != null) this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(this.h, type, packetplayinblockdig_enumplayerdigtype, true, "aborted mismatched destroying")); // Paper ++ this.h = BlockPosition.ZERO; // Paper + } + + this.world.a(this.player.getId(), blockposition, -1); +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 69b27924b8f12b647133fedcfb0568698d19f413..59617fceecb08edf665902f30ea7064e6333bb96 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1519,6 +1519,11 @@ public class PlayerConnection implements PacketListenerPlayIn { + case START_DESTROY_BLOCK: + case ABORT_DESTROY_BLOCK: + case STOP_DESTROY_BLOCK: ++ // Paper start - Don't allow digging in unloaded chunks ++ if (this.player.world.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { ++ return; ++ } ++ // Paper end - Don't allow digging in unloaded chunks + this.player.playerInteractManager.a(blockposition, packetplayinblockdig_enumplayerdigtype, packetplayinblockdig.c(), this.minecraftServer.getMaxBuildHeight()); + return; + default: diff --git a/patches/server-unmapped/0001/0313-Book-Size-Limits.patch b/patches/server-unmapped/0001/0313-Book-Size-Limits.patch new file mode 100644 index 0000000000..17535e7624 --- /dev/null +++ b/patches/server-unmapped/0001/0313-Book-Size-Limits.patch @@ -0,0 +1,80 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 3139c194f9b1bc3510d51a81f13ae43d00a3dc29..13edb435b3fa65b4980bd7472aa5a5196f4d5b2b 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -339,4 +339,11 @@ public class PaperConfig { + velocitySecretKey = secret.getBytes(StandardCharsets.UTF_8); + } + } ++ ++ public static int maxBookPageSize = 2560; ++ public static double maxBookTotalSizeMultiplier = 0.98D; ++ private static void maxBookSize() { ++ maxBookPageSize = getInt("settings.book-size.page-max", maxBookPageSize); ++ maxBookTotalSizeMultiplier = getDouble("settings.book-size.total-multiplier", maxBookTotalSizeMultiplier); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 59617fceecb08edf665902f30ea7064e6333bb96..7158d3e3a7e2912d8d170aca4096355d43645cfa 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1001,6 +1001,52 @@ public class PlayerConnection implements PacketListenerPlayIn { + + @Override + public void a(PacketPlayInBEdit packetplayinbedit) { ++ // Paper start ++ ItemStack testStack = packetplayinbedit.getBook(); ++ if (!server.isPrimaryThread() && !testStack.isEmpty() && testStack.getTag() != null) { ++ NBTTagList pageList = testStack.getTag().getList("pages", 8); ++ if (pageList.size() > 100) { ++ PlayerConnection.LOGGER.warn(this.player.getName() + " tried to send a book with too many pages"); ++ minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ return; ++ } ++ long byteTotal = 0; ++ int maxBookPageSize = com.destroystokyo.paper.PaperConfig.maxBookPageSize; ++ double multiplier = Math.max(0.3D, Math.min(1D, com.destroystokyo.paper.PaperConfig.maxBookTotalSizeMultiplier)); ++ long byteAllowed = maxBookPageSize; ++ for (int i = 0; i < pageList.size(); ++i) { ++ String testString = pageList.getString(i); ++ int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; ++ if (byteLength > 256 * 4) { ++ PlayerConnection.LOGGER.warn(this.player.getName() + " tried to send a book with with a page too large!"); ++ minecraftServer.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) { ++ PlayerConnection.LOGGER.warn(this.player.getName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); ++ minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ return; ++ } ++ } ++ // Paper end + // CraftBukkit start + if (this.lastBookTick + 20 > MinecraftServer.currentTick) { + this.disconnect("Book edited too quickly!"); diff --git a/patches/server-unmapped/0001/0314-Make-the-default-permission-message-configurable.patch b/patches/server-unmapped/0001/0314-Make-the-default-permission-message-configurable.patch new file mode 100644 index 0000000000..f7f5564987 --- /dev/null +++ b/patches/server-unmapped/0001/0314-Make-the-default-permission-message-configurable.patch @@ -0,0 +1,46 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 13edb435b3fa65b4980bd7472aa5a5196f4d5b2b..469f78775b03cf363d88e35c69c0dc185c22547c 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -19,6 +19,7 @@ import java.util.regex.Pattern; + import com.google.common.collect.Lists; + import net.minecraft.server.MinecraftServer; + import org.bukkit.Bukkit; ++import org.bukkit.ChatColor; + import org.bukkit.command.Command; + import org.bukkit.configuration.ConfigurationSection; + import org.bukkit.configuration.InvalidConfigurationException; +@@ -287,6 +288,11 @@ public class PaperConfig { + connectionThrottleKickMessage = getString("messages.kick.connection-throttle", connectionThrottleKickMessage); + } + ++ public static String noPermissionMessage = "&cI'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."; ++ private static void noPermissionMessage() { ++ noPermissionMessage = ChatColor.translateAlternateColorCodes('&', getString("messages.no-permission", noPermissionMessage)); ++ } ++ + private static void savePlayerData() { + Object val = config.get("settings.save-player-data"); + if (val instanceof Boolean) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index ed541a9782d09d3537e04e3651833208a83d7b12..0b16f2b3dad8f391a4067cbac99a8d01acbaacb4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2335,6 +2335,11 @@ public final class CraftServer implements Server { + return com.destroystokyo.paper.PaperConfig.suggestPlayersWhenNullTabCompletions; + } + ++ @Override ++ public String getPermissionMessage() { ++ return com.destroystokyo.paper.PaperConfig.noPermissionMessage; ++ } ++ + @Override + public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { + return createProfile(uuid, null); diff --git a/patches/server-unmapped/0001/0315-Prevent-rayTrace-from-loading-chunks.patch b/patches/server-unmapped/0001/0315-Prevent-rayTrace-from-loading-chunks.patch new file mode 100644 index 0000000000..0295326280 --- /dev/null +++ b/patches/server-unmapped/0001/0315-Prevent-rayTrace-from-loading-chunks.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 26 Nov 2018 19:21:58 -0500 +Subject: [PATCH] Prevent rayTrace from loading chunks + +ray tracing into an unloaded chunk should be treated as a miss +this saves a ton of lag for when AI tries to raytrace near unloaded chunks. + +diff --git a/src/main/java/net/minecraft/world/level/IBlockAccess.java b/src/main/java/net/minecraft/world/level/IBlockAccess.java +index e799765ecfada1eec78beb71651e52ad355a30aa..21ce19b9caf3150535a3f84027242a93bdd0d263 100644 +--- a/src/main/java/net/minecraft/world/level/IBlockAccess.java ++++ b/src/main/java/net/minecraft/world/level/IBlockAccess.java +@@ -58,7 +58,15 @@ public interface IBlockAccess { + + // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace + default MovingObjectPositionBlock rayTraceBlock(RayTrace raytrace1, BlockPosition blockposition) { +- IBlockData iblockdata = this.getType(blockposition); ++ // Paper start - Prevent raytrace from loading chunks ++ IBlockData iblockdata = this.getTypeIfLoaded(blockposition); ++ if (iblockdata == null) { ++ // copied the last function parameter (listed below) ++ Vec3D vec3d = raytrace1.b().d(raytrace1.a()); ++ ++ return MovingObjectPositionBlock.a(raytrace1.a(), EnumDirection.a(vec3d.x, vec3d.y, vec3d.z), new BlockPosition(raytrace1.a())); ++ } ++ // Paper end + Fluid fluid = this.getFluid(blockposition); + Vec3D vec3d = raytrace1.b(); + Vec3D vec3d1 = raytrace1.a(); diff --git a/patches/server-unmapped/0001/0316-Handle-Large-Packets-disconnecting-client.patch b/patches/server-unmapped/0001/0316-Handle-Large-Packets-disconnecting-client.patch new file mode 100644 index 0000000000..9f62fd514e --- /dev/null +++ b/patches/server-unmapped/0001/0316-Handle-Large-Packets-disconnecting-client.patch @@ -0,0 +1,130 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 27 Nov 2018 21:18:06 -0500 +Subject: [PATCH] Handle Large Packets disconnecting client + +If a players inventory is too big to send in a single packet, +split the inventory set into multiple packets instead. + +diff --git a/src/main/java/net/minecraft/network/NetworkManager.java b/src/main/java/net/minecraft/network/NetworkManager.java +index 297820baef99e97e1216a64c527219e9ccc3e320..dc788d75a6a34fbbae990609bfbbd13ca6cdee5a 100644 +--- a/src/main/java/net/minecraft/network/NetworkManager.java ++++ b/src/main/java/net/minecraft/network/NetworkManager.java +@@ -12,6 +12,7 @@ import io.netty.channel.epoll.EpollEventLoopGroup; + import io.netty.channel.local.LocalChannel; + import io.netty.channel.local.LocalServerChannel; + import io.netty.channel.nio.NioEventLoopGroup; ++import io.netty.handler.codec.EncoderException; // Paper + import io.netty.handler.timeout.TimeoutException; + import io.netty.util.AttributeKey; + import io.netty.util.concurrent.Future; +@@ -107,6 +108,15 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + } + + public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) { ++ // Paper start ++ if (throwable instanceof EncoderException && throwable.getCause() instanceof PacketEncoder.PacketTooLargeException) { ++ if (((PacketEncoder.PacketTooLargeException) throwable.getCause()).getPacket().packetTooLarge(this)) { ++ return; ++ } else { ++ throwable = throwable.getCause(); ++ } ++ } ++ // Paper end + if (throwable instanceof SkipEncodeException) { + NetworkManager.LOGGER.debug("Skipping packet due to errors", throwable.getCause()); + } else { +diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java +index 2f6da89d6b25ba5144ec15b1bf0e8ed13278e85e..037d989522d24a55eced1c462d40a6dc2a7ecfce 100644 +--- a/src/main/java/net/minecraft/network/PacketEncoder.java ++++ b/src/main/java/net/minecraft/network/PacketEncoder.java +@@ -53,7 +53,31 @@ public class PacketEncoder extends MessageToByteEncoder> { + throw throwable; + } + } ++ ++ // Paper start ++ int packetLength = bytebuf.readableBytes(); ++ if (packetLength > MAX_PACKET_SIZE) { ++ throw new PacketTooLargeException(packet, packetLength); ++ } ++ // Paper end + } + } + } ++ ++ // Paper start ++ private static int MAX_PACKET_SIZE = 2097152; ++ ++ public static class PacketTooLargeException extends RuntimeException { ++ private final Packet packet; ++ ++ PacketTooLargeException(Packet packet, int packetLength) { ++ super("PacketTooLarge - " + packet.getClass().getSimpleName() + " is " + packetLength + ". Max is " + MAX_PACKET_SIZE); ++ this.packet = packet; ++ } ++ ++ public Packet getPacket() { ++ return packet; ++ } ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java +index cd4493a023748264748d4e892815f14d8a7bd7f6..0783b0777c8d7788bbf6780b464b709bf6dc2191 100644 +--- a/src/main/java/net/minecraft/network/protocol/Packet.java ++++ b/src/main/java/net/minecraft/network/protocol/Packet.java +@@ -12,6 +12,12 @@ public interface Packet { + + void a(T t0); + ++ // Paper start ++ default boolean packetTooLarge(NetworkManager manager) { ++ return false; ++ } ++ // Paper end ++ + default boolean a() { + return false; + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +index 152118729b1a95dcae05d32aa4289034ba394226..0059ede4ba3ff271d47dd38ea87fddc2399aa008 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +@@ -91,7 +91,7 @@ public class PacketPlayOutMapChunk implements Packet { + + int i = packetdataserializer.i(); + +- if (i > 2097152) { ++ if (i > 2097152) { // Paper - if this changes, update PacketEncoder + throw new RuntimeException("Chunk Packet trying to allocate too much memory on read."); + } else { + this.f = new byte[i]; +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutWindowItems.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutWindowItems.java +index b90e35a0099a2482f8fc2998bd079fc2fe6439e6..e540f95a370c627d3d81e0d3670049d01923559d 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutWindowItems.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutWindowItems.java +@@ -4,6 +4,7 @@ import java.io.IOException; + import java.util.Iterator; + import java.util.List; + import net.minecraft.core.NonNullList; ++import net.minecraft.network.NetworkManager; + import net.minecraft.network.PacketDataSerializer; + import net.minecraft.network.protocol.Packet; + import net.minecraft.world.item.ItemStack; +@@ -13,6 +14,15 @@ public class PacketPlayOutWindowItems implements Packet { + private int a; + private List b; + ++ //Paper start ++ @Override ++ public boolean packetTooLarge(NetworkManager manager) { ++ for (int i = 0 ; i < this.b.size() ; i++) { ++ manager.sendPacket(new PacketPlayOutSetSlot(this.a, i, this.b.get(i))); ++ } ++ return true; ++ } ++ // Paper end + public PacketPlayOutWindowItems() {} + + public PacketPlayOutWindowItems(int i, NonNullList nonnulllist) { diff --git a/patches/server-unmapped/0001/0317-force-entity-dismount-during-teleportation.patch b/patches/server-unmapped/0001/0317-force-entity-dismount-during-teleportation.patch new file mode 100644 index 0000000000..89c1aa50ce --- /dev/null +++ b/patches/server-unmapped/0001/0317-force-entity-dismount-during-teleportation.patch @@ -0,0 +1,134 @@ +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/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index fbc0c81bb7e87f7820325a9a7bb39123db272845..1bf0e7ca544aa377005dfcc197f136ecef019e3a 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -1252,11 +1252,13 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + } + +- @Override +- public void stopRiding() { ++ // Paper start ++ @Override public void stopRiding() { stopRiding(false); } ++ @Override public void stopRiding(boolean suppressCancellation) { ++ // paper end + Entity entity = this.getVehicle(); + +- super.stopRiding(); ++ super.stopRiding(suppressCancellation); // Paper + Entity entity1 = this.getVehicle(); + + if (entity1 != entity && this.playerConnection != null) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0b61d03506bd56cf7e373daacbf4fb2e29bb0a58..1e5930f5ae75b82abf6ea2a50558449fb667016f 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2040,12 +2040,15 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + } + +- public void bf() { ++ // Paper start ++ public void bf() { stopRiding(false); } ++ public void stopRiding(boolean suppressCancellation) { ++ // Paper end + 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 + } + + } +@@ -2100,7 +2103,10 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return true; // CraftBukkit + } + +- protected boolean removePassenger(Entity entity) { // CraftBukkit ++ // Paper start ++ protected boolean removePassenger(Entity entity) { return removePassenger(entity, false);} ++ protected boolean removePassenger(Entity entity, boolean suppressCancellation) { // CraftBukkit ++ // Paper end + if (entity.getVehicle() == this) { + throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)"); + } else { +@@ -2110,7 +2116,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + if (getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { + VehicleExitEvent event = new VehicleExitEvent( + (Vehicle) getBukkitEntity(), +- (LivingEntity) entity.getBukkitEntity() ++ (LivingEntity) entity.getBukkitEntity(), !suppressCancellation // Paper + ); + // Suppress during worldgen + if (this.valid) { +@@ -2124,7 +2130,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + // CraftBukkit end + // Spigot start +- org.spigotmc.event.entity.EntityDismountEvent event = new org.spigotmc.event.entity.EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity()); ++ org.spigotmc.event.entity.EntityDismountEvent event = new org.spigotmc.event.entity.EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper + // Suppress during worldgen + if (this.valid) { + Bukkit.getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index b3c2976a48c2349e5c22d58dd1ac64a02cd969d5..6ada2fe58966553b52a8a890088c78d5ea0dfd73 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -3015,11 +3015,13 @@ public abstract class EntityLiving extends Entity { + return ((Byte) this.datawatcher.get(EntityLiving.ag) & 4) != 0; + } + +- @Override +- public void stopRiding() { ++ // Paper start ++ @Override public void stopRiding() { stopRiding(false); } ++ @Override public void stopRiding(boolean suppressCancellation) { ++ // Paper end + Entity entity = this.getVehicle(); + +- super.stopRiding(); ++ super.stopRiding(suppressCancellation); // Paper - suppress + if (entity != null && entity != this.getVehicle() && !this.world.isClientSide) { + this.a(entity); + } +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 87374174dcbf9e7ee448a1cdd9a3528557c3a2ea..564dfa98c166fde509044e6e1938efb321ece53d 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -1039,9 +1039,11 @@ public abstract class EntityHuman extends EntityLiving { + return -0.35D; + } + +- @Override +- public void bf() { +- super.bf(); ++ // Paper start ++ @Override public void bf() { stopRiding(false); } ++ @Override public void stopRiding(boolean suppressCancellation) { ++ // Paper end ++ super.stopRiding(suppressCancellation); // Paper - suppress + this.j = 0; + } + diff --git a/patches/server-unmapped/0001/0318-Add-more-Zombie-API.patch b/patches/server-unmapped/0001/0318-Add-more-Zombie-API.patch new file mode 100644 index 0000000000..c620328b1a --- /dev/null +++ b/patches/server-unmapped/0001/0318-Add-more-Zombie-API.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 7 Oct 2018 04:29:59 -0500 +Subject: [PATCH] Add more Zombie API + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +index 219e3b1626d68ede57b08a706d24bb6bc4b13fac..79d34b0296f88e5b4fe0351fe2483fc0128deabc 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +@@ -94,6 +94,7 @@ public class EntityZombie extends EntityMonster { + private int bt; + public int drownedConversionTime; + private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field ++ private boolean shouldBurnInDay = true; // Paper + + public EntityZombie(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -262,6 +263,12 @@ public class EntityZombie extends EntityMonster { + super.movementTick(); + } + ++ // Paper start ++ public void stopDrowning() { ++ this.drownedConversionTime = -1; ++ this.getDataWatcher().set(EntityZombie.DROWN_CONVERTING, false); ++ } ++ // Paper end + public void startDrownedConversion(int i) { + this.lastTick = MinecraftServer.currentTick; // CraftBukkit + this.drownedConversionTime = i; +@@ -290,9 +297,16 @@ public class EntityZombie extends EntityMonster { + + } + ++ public boolean shouldBurnInDay() { return T_(); } // Paper - OBFHELPER + protected boolean T_() { +- return true; ++ return this.shouldBurnInDay; // Paper - use api value instead ++ } ++ ++ // Paper start ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ this.shouldBurnInDay = shouldBurnInDay; + } ++ // Paper end + + @Override + public boolean damageEntity(DamageSource damagesource, float f) { +@@ -413,6 +427,7 @@ public class EntityZombie extends EntityMonster { + nbttagcompound.setBoolean("CanBreakDoors", this.eU()); + nbttagcompound.setInt("InWaterTime", this.isInWater() ? this.bt : -1); + nbttagcompound.setInt("DrownedConversionTime", this.isDrownConverting() ? this.drownedConversionTime : -1); ++ nbttagcompound.setBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); // Paper + } + + @Override +@@ -424,7 +439,11 @@ public class EntityZombie extends EntityMonster { + if (nbttagcompound.hasKeyOfType("DrownedConversionTime", 99) && nbttagcompound.getInt("DrownedConversionTime") > -1) { + this.startDrownedConversion(nbttagcompound.getInt("DrownedConversionTime")); + } +- ++ // Paper start ++ if (nbttagcompound.hasKey("Paper.ShouldBurnInDay")) { ++ shouldBurnInDay = nbttagcompound.getBoolean("Paper.ShouldBurnInDay"); ++ } ++ // Paper end + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +index 8ba92b7edacc088d610a9ed46eb7f61ebd8101a8..42d98d798bb8fe2d3c7cc2bfcf2ec38d97d99bd2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +@@ -94,6 +94,42 @@ public class CraftZombie extends CraftMonster implements Zombie { + @Override + public void setAgeLock(boolean b) { + } ++ // Paper start ++ @Override ++ public boolean isDrowning() { ++ return getHandle().isDrownConverting(); ++ } ++ ++ @Override ++ public void startDrowning(int drownedConversionTime) { ++ getHandle().startDrownedConversion(drownedConversionTime); ++ } ++ ++ @Override ++ public void stopDrowning() { ++ getHandle().stopDrowning(); ++ } ++ ++ @Override ++ public boolean shouldBurnInDay() { ++ return getHandle().shouldBurnInDay(); ++ } ++ ++ @Override ++ public boolean isArmsRaised() { ++ return getHandle().isAggressive(); ++ } ++ ++ @Override ++ public void setArmsRaised(final boolean raised) { ++ getHandle().setAggressive(raised); ++ } ++ ++ @Override ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ getHandle().setShouldBurnInDay(shouldBurnInDay); ++ } ++ // Paper end + + @Override + public boolean getAgeLock() { diff --git a/patches/server-unmapped/0001/0319-Add-PlayerConnectionCloseEvent.patch b/patches/server-unmapped/0001/0319-Add-PlayerConnectionCloseEvent.patch new file mode 100644 index 0000000000..292fb6107f --- /dev/null +++ b/patches/server-unmapped/0001/0319-Add-PlayerConnectionCloseEvent.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 7 Oct 2018 12:05:28 -0700 +Subject: [PATCH] Add PlayerConnectionCloseEvent + +This event is invoked when a player has disconnected. It is guaranteed that, +if the server is in online-mode, that the provided uuid and username have been +validated. + +The event is invoked for players who have not yet logged into the world, whereas +PlayerQuitEvent is only invoked on players who have logged into the world. + +The event is invoked for players who have already logged into the world, +although whether or not the player exists in the world at the time of +firing is undefined. (That is, whether the plugin can retrieve a Player object +using the event parameters is undefined). However, it is guaranteed that this +event is invoked AFTER PlayerQuitEvent, if the player has already logged into +the world. + +This event is guaranteed to never fire unless AsyncPlayerPreLoginEvent has +been called beforehand, and this event may not be called in parallel with +AsyncPlayerPreLoginEvent for the same connection. + +Cancelling the AsyncPlayerPreLoginEvent guarantees the corresponding +PlayerConnectionCloseEvent is never called. + +The event may be invoked asynchronously or synchronously. As it stands, +it is never invoked asynchronously. However, plugins should check +Event#isAsynchronous to be future-proof. + +On purpose, the deprecated PlayerPreLoginEvent event is left out of the +API spec for this event. Plugins should not be using that event, and +how PlayerPreLoginEvent interacts with PlayerConnectionCloseEvent +is undefined. + +diff --git a/src/main/java/net/minecraft/network/NetworkManager.java b/src/main/java/net/minecraft/network/NetworkManager.java +index dc788d75a6a34fbbae990609bfbbd13ca6cdee5a..6d40ade5a52383ed86d28d272c3dc83dbdcbd218 100644 +--- a/src/main/java/net/minecraft/network/NetworkManager.java ++++ b/src/main/java/net/minecraft/network/NetworkManager.java +@@ -350,6 +350,26 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + this.j().a(new ChatMessage("multiplayer.disconnect.generic")); + } + this.packetQueue.clear(); // Free up packet queue. ++ // Paper start - Add PlayerConnectionCloseEvent ++ final PacketListener packetListener = this.j(); ++ if (packetListener instanceof PlayerConnection) { ++ /* Player was logged in */ ++ final PlayerConnection playerConnection = (PlayerConnection) packetListener; ++ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(playerConnection.player.getUniqueID(), ++ playerConnection.player.getName(), ((java.net.InetSocketAddress)socketAddress).getAddress(), false).callEvent(); ++ } else if (packetListener instanceof LoginListener) { ++ /* Player is login stage */ ++ final LoginListener loginListener = (LoginListener) packetListener; ++ switch (loginListener.getLoginState()) { ++ case READY_TO_ACCEPT: ++ case DELAY_ACCEPT: ++ case ACCEPTED: ++ final com.mojang.authlib.GameProfile profile = loginListener.getGameProfile(); /* Should be non-null at this stage */ ++ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(profile.getId(), profile.getName(), ++ ((java.net.InetSocketAddress)socketAddress).getAddress(), false).callEvent(); ++ } ++ } ++ // Paper end + } + + } +diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java +index a3c989ef850919fa90590b942f037c1f6d519608..4dd2f7fb32b8618d752e0988acadcb41223c0e4c 100644 +--- a/src/main/java/net/minecraft/server/network/LoginListener.java ++++ b/src/main/java/net/minecraft/server/network/LoginListener.java +@@ -57,9 +57,9 @@ public class LoginListener implements PacketLoginInListener { + private final byte[] e = new byte[4]; + private final MinecraftServer server; + public final NetworkManager networkManager; +- private LoginListener.EnumProtocolState g; ++ private LoginListener.EnumProtocolState g; public final LoginListener.EnumProtocolState getLoginState() { return this.g; }; // Paper - OBFHELPER + private int h; +- private GameProfile i; private void setGameProfile(final GameProfile profile) { this.i = profile; } private GameProfile getGameProfile() { return this.i; } // Paper - OBFHELPER ++ private GameProfile i; private void setGameProfile(final GameProfile profile) { this.i = profile; } public GameProfile getGameProfile() { return this.i; } // Paper - OBFHELPER + private final String j; + private SecretKey loginKey; + private EntityPlayer l; diff --git a/patches/server-unmapped/0001/0320-Prevent-Enderman-from-loading-chunks.patch b/patches/server-unmapped/0001/0320-Prevent-Enderman-from-loading-chunks.patch new file mode 100644 index 0000000000..86be24f3d4 --- /dev/null +++ b/patches/server-unmapped/0001/0320-Prevent-Enderman-from-loading-chunks.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 18 Dec 2018 02:15:08 +0000 +Subject: [PATCH] Prevent Enderman from loading chunks + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java b/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java +index aa6cb15637144c9d8db1b1861e58f3f02d68357a..e993b1849beb60515c51ee4f37617faab63ca223 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityEnderman.java +@@ -434,7 +434,8 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + int j = MathHelper.floor(this.enderman.locY() + random.nextDouble() * 3.0D); + int k = MathHelper.floor(this.enderman.locZ() - 2.0D + random.nextDouble() * 4.0D); + BlockPosition blockposition = new BlockPosition(i, j, k); +- IBlockData iblockdata = world.getType(blockposition); ++ IBlockData iblockdata = world.getTypeIfLoaded(blockposition); // Paper ++ if (iblockdata == null) return; // Paper + Block block = iblockdata.getBlock(); + Vec3D vec3d = new Vec3D((double) MathHelper.floor(this.enderman.locX()) + 0.5D, (double) j + 0.5D, (double) MathHelper.floor(this.enderman.locZ()) + 0.5D); + Vec3D vec3d1 = new Vec3D((double) i + 0.5D, (double) j + 0.5D, (double) k + 0.5D); +@@ -474,7 +475,8 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + int j = MathHelper.floor(this.a.locY() + random.nextDouble() * 2.0D); + int k = MathHelper.floor(this.a.locZ() - 1.0D + random.nextDouble() * 2.0D); + BlockPosition blockposition = new BlockPosition(i, j, k); +- IBlockData iblockdata = world.getType(blockposition); ++ IBlockData iblockdata = world.getTypeIfLoaded(blockposition); // Paper ++ if (iblockdata == null) return; // Paper + BlockPosition blockposition1 = blockposition.down(); + IBlockData iblockdata1 = world.getType(blockposition1); + IBlockData iblockdata2 = this.a.getCarried(); diff --git a/patches/server-unmapped/0001/0321-Add-APIs-to-replace-OfflinePlayer-getLastPlayed.patch b/patches/server-unmapped/0001/0321-Add-APIs-to-replace-OfflinePlayer-getLastPlayed.patch new file mode 100644 index 0000000000..2667b9b4d4 --- /dev/null +++ b/patches/server-unmapped/0001/0321-Add-APIs-to-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] Add APIs to 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/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 1bf0e7ca544aa377005dfcc197f136ecef019e3a..97aae1d2a512e6197ca3e491d041efd2def6e37a 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -213,6 +213,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public int ping; + public boolean viewingCredits; + private int containerUpdateDelay; // Paper ++ public long loginTime; // Paper + // Paper start - cancellable death event + public boolean queueHealthUpdatePacket = false; + public net.minecraft.network.protocol.game.PacketPlayOutUpdateHealth queuedHealthUpdatePacket; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 1faae8a451c25cc8e37ef1907206a4f721477b13..e58784539bb1cc66581317c7167ae3326d5622ec 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -168,6 +168,7 @@ public abstract class PlayerList { + } + + public void a(NetworkManager networkmanager, EntityPlayer entityplayer) { ++ entityplayer.loginTime = System.currentTimeMillis(); // Paper + GameProfile gameprofile = entityplayer.getProfile(); + UserCache usercache = this.server.getUserCache(); + GameProfile gameprofile1 = usercache.getProfile(gameprofile.getId()); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +index 9b8d7b176e288fa715177196e7aff92900d8567a..1e741158bbcc0991259436bec549b32df61f0c54 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -244,6 +244,61 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + return getData() != null; + } + ++ // Paper start ++ @Override ++ public long getLastLogin() { ++ Player player = getPlayer(); ++ if (player != null) return player.getLastLogin(); ++ ++ NBTTagCompound data = getPaperData(); ++ ++ if (data != null) { ++ if (data.hasKey("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(); ++ ++ NBTTagCompound data = getPaperData(); ++ ++ if (data != null) { ++ if (data.hasKey("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 NBTTagCompound getPaperData() { ++ NBTTagCompound result = getData(); ++ ++ if (result != null) { ++ if (!result.hasKey("Paper")) { ++ result.set("Paper", new NBTTagCompound()); ++ } ++ result = result.getCompound("Paper"); ++ } ++ ++ return result; ++ } ++ // Paper end ++ + @Override + public Location getBedSpawnLocation() { + NBTTagCompound data = getData(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 2334a9a95ab0e2395744343a5a1e3d26c88b7dc3..c2ebf264d9d150541aeb2d89f24853c2f887cde5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -147,6 +147,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; + private String resourcePackHash; + 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 end + + public CraftPlayer(CraftServer server, EntityPlayer entity) { +@@ -1485,6 +1486,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.firstPlayed = firstPlayed; + } + ++ // Paper start ++ @Override ++ public long getLastLogin() { ++ return getHandle().loginTime; ++ } ++ ++ @Override ++ public long getLastSeen() { ++ return isOnline() ? System.currentTimeMillis() : this.lastSaveTime; ++ } ++ // Paper end ++ + public void readExtraData(NBTTagCompound nbttagcompound) { + hasPlayedBefore = true; + if (nbttagcompound.hasKey("bukkit")) { +@@ -1507,6 +1520,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void setExtraData(NBTTagCompound nbttagcompound) { ++ this.lastSaveTime = System.currentTimeMillis(); // Paper ++ + if (!nbttagcompound.hasKey("bukkit")) { + nbttagcompound.set("bukkit", new NBTTagCompound()); + } +@@ -1521,6 +1536,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + data.setLong("firstPlayed", getFirstPlayed()); + data.setLong("lastPlayed", System.currentTimeMillis()); + data.setString("lastKnownName", handle.getName()); ++ ++ // Paper start - persist for use in offline save data ++ if (!nbttagcompound.hasKey("Paper")) { ++ nbttagcompound.set("Paper", new NBTTagCompound()); ++ } ++ ++ NBTTagCompound paper = nbttagcompound.getCompound("Paper"); ++ paper.setLong("LastLogin", handle.loginTime); ++ paper.setLong("LastSeen", System.currentTimeMillis()); ++ // Paper end + } + + @Override diff --git a/patches/server-unmapped/0001/0322-Workaround-for-vehicle-tracking-issue-on-disconnect.patch b/patches/server-unmapped/0001/0322-Workaround-for-vehicle-tracking-issue-on-disconnect.patch new file mode 100644 index 0000000000..0add3e43e5 --- /dev/null +++ b/patches/server-unmapped/0001/0322-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/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 97aae1d2a512e6197ca3e491d041efd2def6e37a..050930ffd940c40fc057054f11b86a7042c8ec55 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -1518,6 +1518,13 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public void p() { + this.ch = true; + this.ejectPassengers(); ++ ++ // Paper start - Workaround an issue where the vehicle doesn't track the passenger disconnection dismount. ++ if (this.isPassenger() && this.getVehicle() instanceof EntityPlayer) { ++ this.stopRiding(); ++ } ++ // Paper end ++ + if (this.isSleeping()) { + this.wakeup(true, false); + } diff --git a/patches/server-unmapped/0001/0323-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch b/patches/server-unmapped/0001/0323-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch new file mode 100644 index 0000000000..79adf82b5f --- /dev/null +++ b/patches/server-unmapped/0001/0323-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 31 Jan 2019 16:33:36 -0500 +Subject: [PATCH] Fire BlockPistonRetractEvent for all empty pistons + +There is an explicit check in the handling code for empty pistons that +prevents sticky pistons from firing the event. However when we look back +at the history we see that this check was originally added so that ONLY +sticky pistons would fire the retract event. I'm not sure why. +https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/commits/1092acbddf07edfa4100bc6824504ac75088e913 + +Over the course of several updates, the meaning of that field appears to +have changed from "is NOT sticky" to "is sticky". So now its having the +opposite effect. Only normal pistons fire the retraction event. And like +all things in CB, it's just been carried around since. + +If we are to believe the history, the correct fix for this issue is to +flip it so it only fires for sticky pistons, but that puts us in a +bind. It's already firing for non-sticky pistons, changing it now would +likely result in breakage. Furthermore, there is little documentation as +to WHY that was ever intended to be the case. + +Instead we opt to remove the check entirely so that the event fires for +all piston types. + +diff --git a/src/main/java/net/minecraft/world/level/block/piston/BlockPiston.java b/src/main/java/net/minecraft/world/level/block/piston/BlockPiston.java +index 869aaae93bc83b8271644147bb569c868ab13fb8..7de86d6232eb84642fb6423a1b0a9f30d9df9f2b 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/BlockPiston.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/BlockPiston.java +@@ -141,7 +141,7 @@ public class BlockPiston extends BlockDirectional { + } + + // CraftBukkit start +- if (!this.sticky) { ++ //if (!this.sticky) { // Paper - Prevents empty sticky pistons from firing retract - history behind is odd + org.bukkit.block.Block block = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); + BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumdirection)); + world.getServer().getPluginManager().callEvent(event); +@@ -149,7 +149,7 @@ public class BlockPiston extends BlockDirectional { + if (event.isCancelled()) { + return; + } +- } ++ //} // Paper + // PAIL: checkME - what happened to setTypeAndData? + // CraftBukkit end + world.playBlockAction(blockposition, this, b0, enumdirection.c()); diff --git a/patches/server-unmapped/0001/0324-Block-Entity-remove-from-being-called-on-Players.patch b/patches/server-unmapped/0001/0324-Block-Entity-remove-from-being-called-on-Players.patch new file mode 100644 index 0000000000..59f9e671b8 --- /dev/null +++ b/patches/server-unmapped/0001/0324-Block-Entity-remove-from-being-called-on-Players.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 4 Feb 2019 23:33:24 -0500 +Subject: [PATCH] Block Entity#remove from being called on Players + +This doesn't result in the same behavior as other entities and causes +several problems. Anyone ever complain about the "Cannot send chat +message" thing? That's one of the issues this causes, among others. + +If a plugin developer can come up with a valid reason to call this on a +Player we will look at limiting the scope of this change. It appears to +be unintentional in the few cases we've seen so far. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index c2ebf264d9d150541aeb2d89f24853c2f887cde5..e645a3386df6334e99d80ec6961399461c9545a9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2231,6 +2231,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + public void resetCooldown() { + getHandle().resetAttackCooldown(); + } ++ ++ @Override ++ public void remove() { ++ if (this.getHandle().getClass().equals(EntityPlayer.class)) { // special case for NMS plugins inheriting ++ throw new UnsupportedOperationException("Calling Entity#remove on players produces undefined (bad) behavior"); ++ } else { ++ super.remove(); ++ } ++ } + // Paper end + + // Spigot start diff --git a/patches/server-unmapped/0001/0325-BlockDestroyEvent.patch b/patches/server-unmapped/0001/0325-BlockDestroyEvent.patch new file mode 100644 index 0000000000..0b7edd494a --- /dev/null +++ b/patches/server-unmapped/0001/0325-BlockDestroyEvent.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 6 Feb 2019 00:20:33 -0500 +Subject: [PATCH] BlockDestroyEvent + +Adds an event for when the server is going to destroy a current block, +potentially causing it to drop. This event can be cancelled to avoid +the block destruction, such as preventing signs from popping when +floating in the air. + +This can replace many uses of BlockPhysicsEvent + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 7b6cdf6b1d114aa2d86267972e80d487e92e530e..51cdd8a8609934f97dfc582377b691551fd4849b 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -26,6 +26,7 @@ import net.minecraft.core.particles.ParticleParam; + import net.minecraft.network.protocol.Packet; + import net.minecraft.resources.MinecraftKey; + import net.minecraft.resources.ResourceKey; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.PlayerChunk; +@@ -559,8 +560,20 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return false; + } else { + Fluid fluid = this.getFluid(blockposition); ++ // Paper start - while the above setAir method is named same and looks very similar ++ // they are NOT used with same intent and the above should not fire this event. The above method is more of a BlockSetToAirEvent, ++ // it doesn't imply destruction of a block that plays a sound effect / drops an item. ++ boolean playEffect = true; ++ if (com.destroystokyo.paper.event.block.BlockDestroyEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ com.destroystokyo.paper.event.block.BlockDestroyEvent event = new com.destroystokyo.paper.event.block.BlockDestroyEvent(MCUtil.toBukkitBlock(this, blockposition), fluid.getBlockData().createCraftBlockData(), flag); ++ if (!event.callEvent()) { ++ return false; ++ } ++ playEffect = event.playEffect(); ++ } ++ // Paper end + +- if (!(iblockdata.getBlock() instanceof BlockFireAbstract)) { ++ if (playEffect && !(iblockdata.getBlock() instanceof BlockFireAbstract)) { // Paper + this.triggerEffect(2001, blockposition, Block.getCombinedId(iblockdata)); + } + diff --git a/patches/server-unmapped/0001/0326-Fix-Custom-Shapeless-Custom-Crafting-Recipes.patch b/patches/server-unmapped/0001/0326-Fix-Custom-Shapeless-Custom-Crafting-Recipes.patch new file mode 100644 index 0000000000..e98607e7c3 --- /dev/null +++ b/patches/server-unmapped/0001/0326-Fix-Custom-Shapeless-Custom-Crafting-Recipes.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 18 Jan 2019 00:08:15 -0500 +Subject: [PATCH] Fix Custom Shapeless Custom Crafting Recipes + +Mojang implemented Shapeless different than Shaped + +This made the Bukkit RecipeChoice API not work for Shapeless. + +This reimplements vanilla logic using the same test logic as Shaped + +diff --git a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipes.java b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipes.java +index 9226602691ccbf39835f2dd071f171c2375b0a16..e112d149fc3a7af7f0c9a5280c94c9b03b2aba2d 100644 +--- a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipes.java ++++ b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipes.java +@@ -72,16 +72,49 @@ public class ShapelessRecipes implements RecipeCrafting { + AutoRecipeStackManager autorecipestackmanager = new AutoRecipeStackManager(); + int i = 0; + ++ // Paper start ++ java.util.List providedItems = new java.util.ArrayList<>(); ++ co.aikar.util.Counter matchedProvided = new co.aikar.util.Counter<>(); ++ co.aikar.util.Counter matchedIngredients = new co.aikar.util.Counter<>(); ++ // Paper end + for (int j = 0; j < inventorycrafting.getSize(); ++j) { + ItemStack itemstack = inventorycrafting.getItem(j); + + if (!itemstack.isEmpty()) { +- ++i; +- autorecipestackmanager.a(itemstack, 1); ++ // Paper start ++ itemstack = itemstack.cloneItemStack(); ++ providedItems.add(itemstack); ++ for (RecipeItemStack ingredient : ingredients) { ++ if (ingredient.test(itemstack)) { ++ matchedProvided.increment(itemstack); ++ matchedIngredients.increment(ingredient); ++ } ++ } ++ // Paper end + } + } + +- return i == this.ingredients.size() && autorecipestackmanager.a(this, (IntList) null); ++ // Paper start ++ if (matchedProvided.isEmpty() || matchedIngredients.isEmpty()) { ++ return false; ++ } ++ java.util.List ingredients = new java.util.ArrayList<>(this.ingredients); ++ providedItems.sort(java.util.Comparator.comparingInt((ItemStack c) -> (int) matchedProvided.getCount(c)).reversed()); ++ ingredients.sort(java.util.Comparator.comparingInt((RecipeItemStack c) -> (int) matchedIngredients.getCount(c))); ++ ++ PROVIDED: ++ for (ItemStack provided : providedItems) { ++ for (Iterator itIngredient = ingredients.iterator(); itIngredient.hasNext(); ) { ++ RecipeItemStack ingredient = itIngredient.next(); ++ if (ingredient.test(provided)) { ++ itIngredient.remove(); ++ continue PROVIDED; ++ } ++ } ++ return false; ++ } ++ return ingredients.isEmpty(); ++ // Paper end + } + + public ItemStack a(InventoryCrafting inventorycrafting) { diff --git a/patches/server-unmapped/0001/0327-Fix-sign-edit-memory-leak.patch b/patches/server-unmapped/0001/0327-Fix-sign-edit-memory-leak.patch new file mode 100644 index 0000000000..d97a446a84 --- /dev/null +++ b/patches/server-unmapped/0001/0327-Fix-sign-edit-memory-leak.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 28 Feb 2019 00:15:28 -0500 +Subject: [PATCH] Fix sign edit memory leak + +when a player edits a sign, a reference to their Entity is never cleand up. + +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 7158d3e3a7e2912d8d170aca4096355d43645cfa..e54c06b1214091985d9e360592a7759ff45efc75 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -2851,7 +2851,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + + TileEntitySign tileentitysign = (TileEntitySign) tileentity; + +- if (!tileentitysign.d() || tileentitysign.f() != this.player) { ++ if (!tileentitysign.d() || tileentitysign.signEditor == null || !tileentitysign.signEditor.equals(this.player.getUniqueID())) { + PlayerConnection.LOGGER.warn("Player {} just tried to change non-editable sign", this.player.getDisplayName().getString()); + this.sendPacket(tileentity.getUpdatePacket()); // CraftBukkit + return; +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java +index ec550aaa4e7943af4ecdd2275f1f32c21edf770a..6d0fe58d7e574ce5189e1f7a8485060d60b466d9 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java +@@ -30,6 +30,7 @@ public class TileEntitySign extends TileEntity implements ICommandListener { // + private EntityHuman c; + private final FormattedString[] g; + private EnumColor color; ++ public java.util.UUID signEditor; // Paper + + public TileEntitySign() { + super(TileEntityTypes.SIGN); +@@ -131,7 +132,10 @@ public class TileEntitySign extends TileEntity implements ICommandListener { // + } + + public void a(EntityHuman entityhuman) { +- this.c = entityhuman; ++ // Paper start ++ //this.c = entityhuman; ++ signEditor = entityhuman != null ? entityhuman.getUniqueID() : null; ++ // Paper end + } + + public EntityHuman f() { diff --git a/patches/server-unmapped/0001/0328-Limit-Client-Sign-length-more.patch b/patches/server-unmapped/0001/0328-Limit-Client-Sign-length-more.patch new file mode 100644 index 0000000000..a6cd098200 --- /dev/null +++ b/patches/server-unmapped/0001/0328-Limit-Client-Sign-length-more.patch @@ -0,0 +1,54 @@ +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/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index e54c06b1214091985d9e360592a7759ff45efc75..2c949e079e5de86c902b162dfdb6adffe0511448 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -256,6 +256,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + private int E; + private int receivedMovePackets; + private int processedMovePackets; ++ private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); + private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit + + public PlayerConnection(MinecraftServer minecraftserver, NetworkManager networkmanager, EntityPlayer entityplayer) { +@@ -2861,7 +2862,17 @@ public class PlayerConnection implements PacketListenerPlayIn { + List lines = new java.util.ArrayList<>(); + + for (int i = 0; i < list.size(); ++i) { +- lines.add(net.kyori.adventure.text.Component.text(SharedConstants.filterAllowedChatCharacters(list.get(i)))); // Paper - Replaced with anvil color stripping method to stop exploits that allow colored signs to be created. ++ // Paper start - cap line length - modified clients can send longer data than normal ++ String currentLine = list.get(i); ++ if (MAX_SIGN_LINE_LENGTH > 0 && currentLine.length() > MAX_SIGN_LINE_LENGTH) { ++ // This handles multibyte characters as 1 ++ int offset = currentLine.codePoints().limit(MAX_SIGN_LINE_LENGTH).map(Character::charCount).sum(); ++ if (offset < currentLine.length()) { ++ list.set(i, currentLine = currentLine.substring(0, offset)); ++ } ++ } ++ // Paper end ++ lines.add(net.kyori.adventure.text.Component.text(SharedConstants.filterAllowedChatCharacters(currentLine))); // Paper - Replaced with anvil color stripping method to stop exploits that allow colored signs to be created. + } + SignChangeEvent event = new SignChangeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(worldserver, blockposition), this.getPlayer(), lines); + this.server.getPluginManager().callEvent(event); diff --git a/patches/server-unmapped/0001/0329-Don-t-check-ConvertSigns-boolean-every-sign-save.patch b/patches/server-unmapped/0001/0329-Don-t-check-ConvertSigns-boolean-every-sign-save.patch new file mode 100644 index 0000000000..033181274d --- /dev/null +++ b/patches/server-unmapped/0001/0329-Don-t-check-ConvertSigns-boolean-every-sign-save.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 2 Mar 2019 11:11:29 -0500 +Subject: [PATCH] Don't check ConvertSigns boolean every sign save + +property lookups arent super cheap. they synchronize, validate +and check security managers. + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java +index 6d0fe58d7e574ce5189e1f7a8485060d60b466d9..7f78f388584899b13ff983f0dc37c679bfb1507e 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java +@@ -31,6 +31,7 @@ public class TileEntitySign extends TileEntity implements ICommandListener { // + private final FormattedString[] g; + private EnumColor color; + public java.util.UUID signEditor; // Paper ++ private static final boolean CONVERT_LEGACY_SIGNS = Boolean.getBoolean("convertLegacySigns"); // Paper + + public TileEntitySign() { + super(TileEntityTypes.SIGN); +@@ -51,7 +52,7 @@ public class TileEntitySign extends TileEntity implements ICommandListener { // + } + + // CraftBukkit start +- if (Boolean.getBoolean("convertLegacySigns")) { ++ if (CONVERT_LEGACY_SIGNS) { // Paper + nbttagcompound.setBoolean("Bukkit.isConverted", true); + } + // CraftBukkit end diff --git a/patches/server-unmapped/0001/0330-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/patches/server-unmapped/0001/0330-Optimize-Network-Manager-and-add-advanced-packet-sup.patch new file mode 100644 index 0000000000..053340619f --- /dev/null +++ b/patches/server-unmapped/0001/0330-Optimize-Network-Manager-and-add-advanced-packet-sup.patch @@ -0,0 +1,390 @@ +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 + +diff --git a/src/main/java/net/minecraft/network/NetworkManager.java b/src/main/java/net/minecraft/network/NetworkManager.java +index 6d40ade5a52383ed86d28d272c3dc83dbdcbd218..ab70eeaeca222de7de7cab1b3db14b2c4761c3c3 100644 +--- a/src/main/java/net/minecraft/network/NetworkManager.java ++++ b/src/main/java/net/minecraft/network/NetworkManager.java +@@ -25,8 +25,15 @@ import net.minecraft.network.chat.ChatMessage; + import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.network.protocol.EnumProtocolDirection; + import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.game.PacketPlayOutBoss; ++import net.minecraft.network.protocol.game.PacketPlayOutChat; ++import net.minecraft.network.protocol.game.PacketPlayOutKeepAlive; + import net.minecraft.network.protocol.game.PacketPlayOutKickDisconnect; ++import net.minecraft.network.protocol.game.PacketPlayOutTabComplete; ++import net.minecraft.network.protocol.game.PacketPlayOutTitle; + import net.minecraft.server.CancelledPacketHandleException; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.network.LoginListener; + import net.minecraft.server.network.PlayerConnection; + import net.minecraft.util.LazyInitVar; +@@ -75,6 +82,10 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + public int protocolVersion; + public java.net.InetSocketAddress virtualHost; + private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); ++ // Optimize network ++ public boolean isPending = true; ++ public boolean queueImmunity = false; ++ public EnumProtocol protocol; + // Paper end + + public NetworkManager(EnumProtocolDirection enumprotocoldirection) { +@@ -98,6 +109,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + } + + public void setProtocol(EnumProtocol enumprotocol) { ++ protocol = enumprotocol; // Paper + this.channel.attr(NetworkManager.c).set(enumprotocol); + this.channel.config().setAutoRead(true); + NetworkManager.LOGGER.debug("Enabled auto read"); +@@ -168,19 +180,84 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + Validate.notNull(packetlistener, "packetListener", new Object[0]); + this.packetListener = packetlistener; + } ++ // Paper start ++ public EntityPlayer getPlayer() { ++ if (packetListener instanceof PlayerConnection) { ++ return ((PlayerConnection) packetListener).player; ++ } else { ++ return null; ++ } ++ } ++ private static class InnerUtil { // Attempt to hide these methods from ProtocolLib so it doesn't accidently pick them up. ++ private static java.util.List buildExtraPackets(Packet packet) { ++ java.util.List extra = packet.getExtraPackets(); ++ if (extra == null || extra.isEmpty()) { ++ return null; ++ } ++ java.util.List ret = new java.util.ArrayList<>(1 + extra.size()); ++ buildExtraPackets0(extra, ret); ++ return ret; ++ } ++ ++ private static void buildExtraPackets0(java.util.List extraPackets, java.util.List into) { ++ for (Packet extra : extraPackets) { ++ into.add(extra); ++ java.util.List extraExtra = extra.getExtraPackets(); ++ if (extraExtra != null && !extraExtra.isEmpty()) { ++ buildExtraPackets0(extraExtra, into); ++ } ++ } ++ } ++ // Paper start ++ private static boolean canSendImmediate(NetworkManager networkManager, Packet packet) { ++ return networkManager.isPending || networkManager.protocol != EnumProtocol.PLAY || ++ packet instanceof PacketPlayOutKeepAlive || ++ packet instanceof PacketPlayOutChat || ++ packet instanceof PacketPlayOutTabComplete || ++ packet instanceof PacketPlayOutTitle || ++ packet instanceof PacketPlayOutBoss; ++ } ++ // Paper end ++ } ++ // Paper end + + public void sendPacket(Packet packet) { + this.sendPacket(packet, (GenericFutureListener) null); + } + + public void sendPacket(Packet packet, @Nullable GenericFutureListener> genericfuturelistener) { +- if (this.isConnected()) { +- this.p(); +- this.b(packet, genericfuturelistener); +- } else { +- this.packetQueue.add(new NetworkManager.QueuedPacket(packet, genericfuturelistener)); ++ // Paper start - handle oversized packets better ++ boolean connected = this.isConnected(); ++ if (!connected && !preparing) { ++ return; // Do nothing ++ } ++ packet.onPacketDispatch(getPlayer()); ++ if (connected && (InnerUtil.canSendImmediate(this, packet) || ( ++ MCUtil.isMainThread() && packet.isReady() && this.packetQueue.isEmpty() && ++ (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) ++ ))) { ++ this.dispatchPacket(packet, genericfuturelistener); ++ return; + } ++ // write the packets to the queue, then flush - antixray hooks there already ++ java.util.List extraPackets = InnerUtil.buildExtraPackets(packet); ++ boolean hasExtraPackets = extraPackets != null && !extraPackets.isEmpty(); ++ if (!hasExtraPackets) { ++ this.packetQueue.add(new NetworkManager.QueuedPacket(packet, genericfuturelistener)); ++ } else { ++ java.util.List packets = new java.util.ArrayList<>(1 + extraPackets.size()); ++ packets.add(new NetworkManager.QueuedPacket(packet, null)); // delay the future listener until the end of the extra packets ++ ++ for (int i = 0, len = extraPackets.size(); i < len;) { ++ Packet extra = extraPackets.get(i); ++ boolean end = ++i == len; ++ packets.add(new NetworkManager.QueuedPacket(extra, end ? genericfuturelistener : null)); // append listener to the end ++ } + ++ this.packetQueue.addAll(packets); // atomic ++ } ++ this.sendPacketQueue(); ++ // Paper end + } + + private void dispatchPacket(Packet packet, @Nullable GenericFutureListener> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER +@@ -194,51 +271,116 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + this.channel.config().setAutoRead(false); + } + ++ EntityPlayer player = getPlayer(); // Paper + if (this.channel.eventLoop().inEventLoop()) { + if (enumprotocol != enumprotocol1) { + this.setProtocol(enumprotocol); + } ++ // Paper start ++ if (!isConnected()) { ++ packet.onPacketDispatchFinish(player, null); ++ return; ++ } ++ try { ++ // Paper end + + ChannelFuture channelfuture = this.channel.writeAndFlush(packet); + + if (genericfuturelistener != null) { + channelfuture.addListener(genericfuturelistener); + } ++ // Paper start ++ if (packet.hasFinishListener()) { ++ channelfuture.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture)); ++ } ++ // Paper end + + channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); ++ // Paper start ++ } catch (Exception e) { ++ LOGGER.error("NetworkException: " + player, e); ++ close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; ++ packet.onPacketDispatchFinish(player, null); ++ } ++ // Paper end + } else { + this.channel.eventLoop().execute(() -> { + if (enumprotocol != enumprotocol1) { + this.setProtocol(enumprotocol); + } + ++ // Paper start ++ if (!isConnected()) { ++ packet.onPacketDispatchFinish(player, null); ++ return; ++ } ++ try { ++ // Paper end + ChannelFuture channelfuture1 = this.channel.writeAndFlush(packet); + ++ + if (genericfuturelistener != null) { + channelfuture1.addListener(genericfuturelistener); + } ++ // Paper start ++ if (packet.hasFinishListener()) { ++ channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture)); ++ } ++ // Paper end + + channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); ++ // Paper start ++ } catch (Exception e) { ++ LOGGER.error("NetworkException: " + player, e); ++ close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; ++ packet.onPacketDispatchFinish(player, null); ++ } ++ // Paper end + }); + } + + } + +- private void sendPacketQueue() { this.p(); } // Paper - OBFHELPER +- private void p() { +- if (this.channel != null && this.channel.isOpen()) { +- Queue queue = this.packetQueue; +- ++ // Paper start - rewrite this to be safer if ran off main thread ++ private boolean sendPacketQueue() { return this.p(); } // OBFHELPER // void -> boolean ++ private boolean p() { // void -> boolean ++ if (!isConnected()) { ++ return true; ++ } ++ if (MCUtil.isMainThread()) { ++ return processQueue(); ++ } else if (isPending) { ++ // Should only happen during login/status stages + synchronized (this.packetQueue) { +- NetworkManager.QueuedPacket networkmanager_queuedpacket; +- +- while ((networkmanager_queuedpacket = (NetworkManager.QueuedPacket) this.packetQueue.poll()) != null) { +- this.b(networkmanager_queuedpacket.a, networkmanager_queuedpacket.b); +- } ++ return this.processQueue(); ++ } ++ } ++ return false; ++ } ++ private boolean processQueue() { ++ if (this.packetQueue.isEmpty()) return true; ++ // 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 ++ java.util.Iterator iterator = this.packetQueue.iterator(); ++ while (iterator.hasNext()) { ++ NetworkManager.QueuedPacket queued = iterator.next(); // poll -> peek ++ ++ // Fix NPE (Spigot bug caused by handleDisconnection()) ++ if (queued == null) { ++ return true; ++ } + ++ Packet packet = queued.getPacket(); ++ if (!packet.isReady()) { ++ return false; ++ } else { ++ iterator.remove(); ++ this.dispatchPacket(packet, queued.getGenericFutureListener()); + } + } ++ return true; + } ++ // Paper end + + public void a() { + this.p(); +@@ -271,9 +413,21 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + return this.socketAddress; + } + ++ // Paper start ++ public void clearPacketQueue() { ++ EntityPlayer player = getPlayer(); ++ packetQueue.forEach(queuedPacket -> { ++ Packet packet = queuedPacket.getPacket(); ++ if (packet.hasFinishListener()) { ++ packet.onPacketDispatchFinish(player, null); ++ } ++ }); ++ packetQueue.clear(); ++ } // Paper end + public void close(IChatBaseComponent ichatbasecomponent) { + // Spigot Start + this.preparing = false; ++ clearPacketQueue(); // Paper + // Spigot End + if (this.channel.isOpen()) { + this.channel.close(); // We can't wait as this may be called from an event loop. +@@ -341,7 +495,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + public void handleDisconnection() { + if (this.channel != null && !this.channel.isOpen()) { + if (this.o) { +- NetworkManager.LOGGER.warn("handleDisconnection() called twice"); ++ //NetworkManager.LOGGER.warn("handleDisconnection() called twice"); // Paper - Do not log useless message + } else { + this.o = true; + if (this.k() != null) { +@@ -349,7 +503,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + } else if (this.j() != null) { + this.j().a(new ChatMessage("multiplayer.disconnect.generic")); + } +- this.packetQueue.clear(); // Free up packet queue. ++ clearPacketQueue(); // Paper + // Paper start - Add PlayerConnectionCloseEvent + final PacketListener packetListener = this.j(); + if (packetListener instanceof PlayerConnection) { +diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java +index 0783b0777c8d7788bbf6780b464b709bf6dc2191..b644c91cecd8a347319dfe8c8923fd05919a9795 100644 +--- a/src/main/java/net/minecraft/network/protocol/Packet.java ++++ b/src/main/java/net/minecraft/network/protocol/Packet.java +@@ -1,5 +1,6 @@ + package net.minecraft.network.protocol; + ++import io.netty.channel.ChannelFuture; // Paper + import java.io.IOException; + import net.minecraft.network.PacketDataSerializer; + import net.minecraft.network.PacketListener; +@@ -13,6 +14,20 @@ public interface Packet { + void a(T t0); + + // Paper start ++ ++ /** ++ * @param player Null if not at PLAY stage yet ++ */ ++ default void onPacketDispatch(@javax.annotation.Nullable EntityPlayer player) {} ++ ++ /** ++ * @param player Null if not at PLAY stage yet ++ * @param future Can be null if packet was cancelled ++ */ ++ default void onPacketDispatchFinish(@javax.annotation.Nullable EntityPlayer player, @javax.annotation.Nullable ChannelFuture future) {} ++ default boolean hasFinishListener() { return false; } ++ default boolean isReady() { return true; } ++ default java.util.List getExtraPackets() { return null; } + default boolean packetTooLarge(NetworkManager manager) { + return false; + } +diff --git a/src/main/java/net/minecraft/server/network/ServerConnection.java b/src/main/java/net/minecraft/server/network/ServerConnection.java +index d992cb5cd827e0fe655809e1088939cdad9c2301..dc362724ea0cc1b2f9d9ceffff483217b4356c40 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnection.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnection.java +@@ -16,6 +16,7 @@ import io.netty.channel.epoll.EpollServerSocketChannel; + import io.netty.channel.nio.NioEventLoopGroup; + import io.netty.channel.socket.ServerSocketChannel; + import io.netty.channel.socket.nio.NioServerSocketChannel; ++import io.netty.handler.flush.FlushConsolidationHandler; // Paper + import io.netty.handler.timeout.ReadTimeoutHandler; + import java.io.IOException; + import java.net.InetAddress; +@@ -54,10 +55,12 @@ public class ServerConnection { + private final List connectedChannels = Collections.synchronizedList(Lists.newArrayList()); + // Paper start - prevent blocking on adding a new network manager 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 + private void addPending() { + NetworkManager manager = null; + while ((manager = pending.poll()) != null) { + connectedChannels.add(manager); ++ manager.isPending = false; + } + } + // Paper end +@@ -92,6 +95,7 @@ public class ServerConnection { + ; + } + ++ if (!disableFlushConsolidation) channel.pipeline().addFirst(new FlushConsolidationHandler()); // Paper + channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("legacy_query", new LegacyPingHandler(ServerConnection.this)).addLast("splitter", new PacketSplitter()).addLast("decoder", new PacketDecoder(EnumProtocolDirection.SERVERBOUND)).addLast("prepender", new PacketPrepender()).addLast("encoder", new PacketEncoder(EnumProtocolDirection.CLIENTBOUND)); + int j = ServerConnection.this.e.k(); + Object object = j > 0 ? new NetworkManagerServer(j) : new NetworkManager(EnumProtocolDirection.SERVERBOUND); diff --git a/patches/server-unmapped/0001/0331-Handle-Oversized-Tile-Entities-in-chunks.patch b/patches/server-unmapped/0001/0331-Handle-Oversized-Tile-Entities-in-chunks.patch new file mode 100644 index 0000000000..ee9073137c --- /dev/null +++ b/patches/server-unmapped/0001/0331-Handle-Oversized-Tile-Entities-in-chunks.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 6 May 2020 05:00:57 -0400 +Subject: [PATCH] Handle Oversized Tile Entities in chunks + +Splits out Extra Packets if too many TE's are encountered to prevent +creating too large of a packet to sed. + +Co authored by Spottedleaf + +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +index 0059ede4ba3ff271d47dd38ea87fddc2399aa008..a7d10d124021f3427f23fcd533f885367b64515c 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +@@ -34,7 +34,15 @@ public class PacketPlayOutMapChunk implements Packet { + private boolean h; + + public PacketPlayOutMapChunk() {} ++ // Paper start ++ private final java.util.List extraPackets = new java.util.ArrayList<>(); ++ private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750); + ++ @Override ++ public java.util.List getExtraPackets() { ++ return extraPackets; ++ } ++ // Paper end + public PacketPlayOutMapChunk(Chunk chunk, int i) { + ChunkCoordIntPair chunkcoordintpair = chunk.getPos(); + +@@ -61,6 +69,7 @@ public class PacketPlayOutMapChunk implements Packet { + this.c = this.a(new PacketDataSerializer(this.j()), chunk, i); + this.g = Lists.newArrayList(); + iterator = chunk.getTileEntities().entrySet().iterator(); ++ int totalTileEntities = 0; // Paper + + while (iterator.hasNext()) { + entry = (Entry) iterator.next(); +@@ -69,6 +78,15 @@ public class PacketPlayOutMapChunk implements Packet { + int j = blockposition.getY() >> 4; + + if (this.f() || (i & 1 << j) != 0) { ++ // Paper start - improve oversized chunk data packet handling ++ if (++totalTileEntities > TE_LIMIT) { ++ PacketPlayOutTileEntityData updatePacket = tileentity.getUpdatePacket(); ++ if (updatePacket != null) { ++ this.extraPackets.add(updatePacket); ++ continue; ++ } ++ } ++ // Paper end + NBTTagCompound nbttagcompound = tileentity.b(); + if (tileentity instanceof TileEntitySkull) { TileEntitySkull.sanitizeTileEntityUUID(nbttagcompound); } // Paper + diff --git a/patches/server-unmapped/0001/0332-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch b/patches/server-unmapped/0001/0332-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch new file mode 100644 index 0000000000..a3aba92ee3 --- /dev/null +++ b/patches/server-unmapped/0001/0332-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 2 Mar 2019 16:12:35 -0500 +Subject: [PATCH] MC-145260: Fix Whitelist On/Off inconsistency + +mojang stored whitelist state in 2 places (Whitelist Object, PlayerList) + +some things checked PlayerList, some checked object. This moves +everything to the Whitelist object. + +https://github.com/PaperMC/Paper/issues/1880 + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index e58784539bb1cc66581317c7167ae3326d5622ec..8183a9dde130fafcee4f342738e9e78e9e67cbec 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -55,6 +55,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutViewDistance; + import net.minecraft.network.protocol.game.PacketPlayOutWorldBorder; + import net.minecraft.resources.ResourceKey; + import net.minecraft.server.AdvancementDataPlayer; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.ScoreboardServer; + import net.minecraft.server.level.DemoPlayerInteractManager; +@@ -1016,9 +1017,9 @@ public abstract class PlayerList { + } + public boolean isWhitelisted(GameProfile gameprofile, org.bukkit.event.player.PlayerLoginEvent loginEvent) { + boolean isOp = this.operators.d(gameprofile); +- boolean isWhitelisted = !this.hasWhitelist || isOp || this.whitelist.d(gameprofile); ++ boolean isWhitelisted = !this.getHasWhitelist() || isOp || this.whitelist.d(gameprofile); + final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event; +- event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(MCUtil.toBukkit(gameprofile), this.hasWhitelist, isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage); ++ event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(MCUtil.toBukkit(gameprofile), this.getHasWhitelist(), isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage); + event.callEvent(); + if (!event.isWhitelisted()) { + if (loginEvent != null) { diff --git a/patches/server-unmapped/0001/0333-Set-Zombie-last-tick-at-start-of-drowning-process.patch b/patches/server-unmapped/0001/0333-Set-Zombie-last-tick-at-start-of-drowning-process.patch new file mode 100644 index 0000000000..30e7bd3924 --- /dev/null +++ b/patches/server-unmapped/0001/0333-Set-Zombie-last-tick-at-start-of-drowning-process.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 4 Mar 2019 02:23:28 -0500 +Subject: [PATCH] Set Zombie last tick at start of drowning process + +Fixes GH-1887 + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +index 79d34b0296f88e5b4fe0351fe2483fc0128deabc..f406826945dd752e6528743a0c8cad3cfdfc4a95 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +@@ -223,6 +223,7 @@ public class EntityZombie extends EntityMonster { + ++this.bt; + if (this.bt >= 600) { + this.startDrownedConversion(300); ++ this.lastTick = MinecraftServer.currentTick; // Paper - Make sure this is set at start of process - GH-1887 + } + } else { + this.bt = -1; diff --git a/patches/server-unmapped/0001/0334-Allow-Saving-of-Oversized-Chunks.patch b/patches/server-unmapped/0001/0334-Allow-Saving-of-Oversized-Chunks.patch new file mode 100644 index 0000000000..56fb7e5bc7 --- /dev/null +++ b/patches/server-unmapped/0001/0334-Allow-Saving-of-Oversized-Chunks.patch @@ -0,0 +1,272 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 15 Feb 2019 01:08:19 -0500 +Subject: [PATCH] Allow Saving of Oversized Chunks + +The Minecraft World Region File format has a hard cap of 1MB per chunk. +This is due to the fact that the header of the file format only allocates +a single byte for sector count, meaning a maximum of 256 sectors, at 4k per sector. + +This limit can be reached fairly easily with books, resulting in the chunk being unable +to save to the world. Worse off, is that nothing printed when this occured, and silently +performed a chunk rollback on next load. + +This leads to security risk with duplication and is being actively exploited. + +This patch catches the too large scenario, falls back and moves any large Entity +or Tile Entity into a new compound, and this compound is saved into a different file. + +On Chunk Load, we check for oversized status, and if so, we load the extra file and +merge the Entities and Tile Entities from the oversized chunk back into the level to +then be loaded as normal. + +Once a chunk is returned back to normal size, the oversized flag will clear, and no +extra data file will exist. + +This fix maintains compatability with all existing Anvil Region Format tools as it +does not alter the save format. They will just not know about the extra entities. + +This fix also maintains compatability if someone switches server jars to one without +this fix, as the data will remain in the oversized file. Once the server returns +to a jar with this fix, the data will be restored. + +diff --git a/src/main/java/net/minecraft/nbt/NBTCompressedStreamTools.java b/src/main/java/net/minecraft/nbt/NBTCompressedStreamTools.java +index 850d3a7bb8ae4c43c0e2f737cfe69261f338b026..20410a5853e34c90c872f5e9592d50c4727e914d 100644 +--- a/src/main/java/net/minecraft/nbt/NBTCompressedStreamTools.java ++++ b/src/main/java/net/minecraft/nbt/NBTCompressedStreamTools.java +@@ -132,6 +132,7 @@ public class NBTCompressedStreamTools { + + } + ++ public static NBTTagCompound readNBT(DataInput datainput) throws IOException { return a(datainput); } // Paper - OBFHELPER + public static NBTTagCompound a(DataInput datainput) throws IOException { + return a(datainput, NBTReadLimiter.a); + } +@@ -152,6 +153,7 @@ public class NBTCompressedStreamTools { + } + } + ++ public static void writeNBT(NBTTagCompound nbttagcompound, DataOutput dataoutput) throws IOException { a(nbttagcompound, dataoutput); } // Paper - OBFHELPER + public static void a(NBTTagCompound nbttagcompound, DataOutput dataoutput) throws IOException { + a((NBTBase) nbttagcompound, dataoutput); + } +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 4bf3e0cb4602d33a2e00c502b1dd212032b22a8f..00cef1c0bc19976a000389e57a1af5d93690c0e7 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 +@@ -20,8 +20,12 @@ import java.nio.file.LinkOption; + import java.nio.file.Path; + import java.nio.file.StandardCopyOption; + import java.nio.file.StandardOpenOption; ++import java.util.zip.InflaterInputStream; // Paper ++ + import javax.annotation.Nullable; + import net.minecraft.SystemUtils; ++import net.minecraft.nbt.NBTCompressedStreamTools; ++import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.world.level.ChunkCoordIntPair; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -38,6 +42,7 @@ public class RegionFile implements AutoCloseable { + private final IntBuffer i; + @VisibleForTesting + protected final RegionFileBitSet freeSectors; ++ public final File file; // Paper + + public RegionFile(File file, File file1, boolean flag) throws IOException { + this(file.toPath(), file1.toPath(), RegionFileCompression.b, flag); +@@ -45,6 +50,8 @@ public class RegionFile implements AutoCloseable { + + public RegionFile(Path path, Path path1, RegionFileCompression regionfilecompression, boolean flag) throws IOException { + this.g = ByteBuffer.allocateDirect(8192); ++ this.file = path.toFile(); // Paper ++ initOversizedState(); // Paper + this.freeSectors = new RegionFileBitSet(); + this.f = regionfilecompression; + if (!Files.isDirectory(path1, new LinkOption[0])) { +@@ -408,6 +415,74 @@ public class RegionFile implements AutoCloseable { + void run() throws IOException; + } + ++ // Paper start ++ private final byte[] oversized = new byte[1024]; ++ private int oversizedCount = 0; ++ ++ private synchronized void initOversizedState() throws IOException { ++ File metaFile = getOversizedMetaFile(); ++ if (metaFile.exists()) { ++ final byte[] read = java.nio.file.Files.readAllBytes(metaFile.toPath()); ++ System.arraycopy(read, 0, oversized, 0, oversized.length); ++ for (byte temp : oversized) { ++ oversizedCount += temp; ++ } ++ } ++ } ++ ++ private static int getChunkIndex(int x, int z) { ++ return (x & 31) + (z & 31) * 32; ++ } ++ synchronized boolean isOversized(int x, int z) { ++ return this.oversized[getChunkIndex(x, z)] == 1; ++ } ++ synchronized void setOversized(int x, int z, boolean oversized) throws IOException { ++ final int offset = getChunkIndex(x, z); ++ boolean previous = this.oversized[offset] == 1; ++ this.oversized[offset] = (byte) (oversized ? 1 : 0); ++ if (!previous && oversized) { ++ oversizedCount++; ++ } else if (!oversized && previous) { ++ oversizedCount--; ++ } ++ if (previous && !oversized) { ++ File oversizedFile = getOversizedFile(x, z); ++ if (oversizedFile.exists()) { ++ oversizedFile.delete(); ++ } ++ } ++ if (oversizedCount > 0) { ++ if (previous != oversized) { ++ writeOversizedMeta(); ++ } ++ } else if (previous) { ++ File oversizedMetaFile = getOversizedMetaFile(); ++ if (oversizedMetaFile.exists()) { ++ oversizedMetaFile.delete(); ++ } ++ } ++ } ++ ++ private void writeOversizedMeta() throws IOException { ++ java.nio.file.Files.write(getOversizedMetaFile().toPath(), oversized); ++ } ++ ++ private File getOversizedMetaFile() { ++ return new File(this.file.getParentFile(), this.file.getName().replaceAll("\\.mca$", "") + ".oversized.nbt"); ++ } ++ ++ private File getOversizedFile(int x, int z) { ++ return new File(this.file.getParentFile(), this.file.getName().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt"); ++ } ++ ++ synchronized NBTTagCompound getOversizedData(int x, int z) throws IOException { ++ File file = getOversizedFile(x, z); ++ try (DataInputStream out = new DataInputStream(new BufferedInputStream(new InflaterInputStream(new java.io.FileInputStream(file))))) { ++ return NBTCompressedStreamTools.readNBT((java.io.DataInput) out); ++ } ++ ++ } ++ // Paper end + class ChunkBuffer extends ByteArrayOutputStream { + + private final ChunkCoordIntPair b; +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java +index 75b10a3755392870d8f5b51239a09a0e7fd75a42..ab9f4d40fd1126a3d7ba5b16fdc6ab09de4a7fdb 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java +@@ -9,8 +9,10 @@ import java.io.DataOutputStream; + import java.io.File; + import java.io.IOException; + import javax.annotation.Nullable; ++import net.minecraft.nbt.NBTBase; + import net.minecraft.nbt.NBTCompressedStreamTools; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.nbt.NBTTagList; + import net.minecraft.server.MinecraftServer; + import net.minecraft.util.ExceptionSuppressor; + import net.minecraft.world.level.ChunkCoordIntPair; +@@ -50,6 +52,74 @@ public final class RegionFileCache implements AutoCloseable { + } + } + ++ // Paper start ++ private static void printOversizedLog(String msg, File file, int x, int z) { ++ org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); ++ } ++ ++ private static final int DEFAULT_SIZE_THRESHOLD = 1024 * 8; ++ private static final int OVERZEALOUS_TOTAL_THRESHOLD = 1024 * 64; ++ private static final int OVERZEALOUS_THRESHOLD = 1024; ++ private static int SIZE_THRESHOLD = DEFAULT_SIZE_THRESHOLD; ++ private static void resetFilterThresholds() { ++ SIZE_THRESHOLD = Math.max(1024 * 4, Integer.getInteger("Paper.FilterThreshhold", DEFAULT_SIZE_THRESHOLD)); ++ } ++ static { ++ resetFilterThresholds(); ++ } ++ ++ static boolean isOverzealous() { ++ return SIZE_THRESHOLD == OVERZEALOUS_THRESHOLD; ++ } ++ ++ ++ private static NBTTagCompound readOversizedChunk(RegionFile regionfile, ChunkCoordIntPair chunkCoordinate) throws IOException { ++ synchronized (regionfile) { ++ try (DataInputStream datainputstream = regionfile.getReadStream(chunkCoordinate)) { ++ NBTTagCompound oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z); ++ NBTTagCompound chunk = NBTCompressedStreamTools.readNBT((DataInput) datainputstream); ++ if (oversizedData == null) { ++ return chunk; ++ } ++ NBTTagCompound oversizedLevel = oversizedData.getCompound("Level"); ++ NBTTagCompound level = chunk.getCompound("Level"); ++ ++ mergeChunkList(level, oversizedLevel, "Entities"); ++ mergeChunkList(level, oversizedLevel, "TileEntities"); ++ ++ chunk.set("Level", level); ++ ++ return chunk; ++ } catch (Throwable throwable) { ++ throwable.printStackTrace(); ++ throw throwable; ++ } ++ } ++ } ++ ++ private static void mergeChunkList(NBTTagCompound level, NBTTagCompound oversizedLevel, String key) { ++ NBTTagList levelList = level.getList(key, 10); ++ NBTTagList oversizedList = oversizedLevel.getList(key, 10); ++ ++ if (!oversizedList.isEmpty()) { ++ levelList.addAll(oversizedList); ++ level.set(key, levelList); ++ } ++ } ++ ++ private static int getNBTSize(NBTBase nbtBase) { ++ DataOutputStream test = new DataOutputStream(new org.apache.commons.io.output.NullOutputStream()); ++ try { ++ nbtBase.write(test); ++ return test.size(); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ return 0; ++ } ++ } ++ ++ // Paper End ++ + @Nullable + public NBTTagCompound read(ChunkCoordIntPair chunkcoordintpair) throws IOException { + // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing +@@ -59,6 +129,12 @@ public final class RegionFileCache implements AutoCloseable { + } + // CraftBukkit end + DataInputStream datainputstream = regionfile.a(chunkcoordintpair); ++ // Paper start ++ if (regionfile.isOversized(chunkcoordintpair.x, chunkcoordintpair.z)) { ++ printOversizedLog("Loading Oversized Chunk!", regionfile.file, chunkcoordintpair.x, chunkcoordintpair.z); ++ return readOversizedChunk(regionfile, chunkcoordintpair); ++ } ++ // Paper end + Throwable throwable = null; + + NBTTagCompound nbttagcompound; +@@ -99,6 +175,7 @@ public final class RegionFileCache implements AutoCloseable { + + try { + NBTCompressedStreamTools.a(nbttagcompound, (DataOutput) dataoutputstream); ++ regionfile.setOversized(chunkcoordintpair.x, chunkcoordintpair.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 + } catch (Throwable throwable1) { + throwable = throwable1; + throw throwable1; diff --git a/patches/server-unmapped/0001/0335-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch b/patches/server-unmapped/0001/0335-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch new file mode 100644 index 0000000000..0c999117c7 --- /dev/null +++ b/patches/server-unmapped/0001/0335-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Wed, 13 Mar 2019 20:08:09 +0200 +Subject: [PATCH] Call WhitelistToggleEvent when whitelist is toggled + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 8183a9dde130fafcee4f342738e9e78e9e67cbec..fa288b099b17adafc085fb0fc5da6e810d078952 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1135,6 +1135,7 @@ public abstract class PlayerList { + } + + public void setHasWhitelist(boolean flag) { ++ new com.destroystokyo.paper.event.server.WhitelistToggleEvent(flag).callEvent(); + this.hasWhitelist = flag; + } + diff --git a/patches/server-unmapped/0001/0336-Add-LivingEntity-getTargetEntity.patch b/patches/server-unmapped/0001/0336-Add-LivingEntity-getTargetEntity.patch new file mode 100644 index 0000000000..d556d1b4ec --- /dev/null +++ b/patches/server-unmapped/0001/0336-Add-LivingEntity-getTargetEntity.patch @@ -0,0 +1,187 @@ +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/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 1e5930f5ae75b82abf6ea2a50558449fb667016f..e9b535622d6c33083c575ee4691598014dba0e2c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1504,6 +1504,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return this.c(f - 90.0F, f1); + } + ++ public final Vec3D getEyePosition(float partialTicks) { return j(partialTicks); } // Paper - OBFHELPER + public final Vec3D j(float f) { + if (f == 1.0F) { + return new Vec3D(this.locX(), this.getHeadY(), this.locZ()); +@@ -2149,6 +2150,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return this.getPassengers().size() < 1; + } + ++ public final float getCollisionBorderSize() { return bg(); } // Paper - OBFHELPER + public float bg() { + return 0.0F; + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 6ada2fe58966553b52a8a890088c78d5ea0dfd73..c98562980ca02c85f2f777a31983c164f2dd5e1e 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -108,6 +108,7 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParameterSet + import net.minecraft.world.level.storage.loot.parameters.LootContextParameters; + import net.minecraft.world.phys.AxisAlignedBB; + import net.minecraft.world.phys.MovingObjectPosition; ++import net.minecraft.world.phys.MovingObjectPositionEntity; + import net.minecraft.world.phys.Vec3D; + import net.minecraft.world.scores.ScoreboardTeam; + import org.apache.logging.log4j.Logger; +@@ -3637,6 +3638,37 @@ public abstract class EntityLiving extends Entity { + return world.rayTrace(raytrace); + } + ++ public MovingObjectPositionEntity getTargetEntity(int maxDistance) { ++ if (maxDistance < 1 || maxDistance > 120) { ++ throw new IllegalArgumentException("maxDistance must be between 1-120"); ++ } ++ ++ Vec3D start = this.getEyePosition(1.0F); ++ Vec3D direction = this.getLookDirection(); ++ Vec3D end = start.add(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance); ++ ++ List entityList = world.getEntities(this, getBoundingBox().expand(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance).grow(1.0D, 1.0D, 1.0D), IEntitySelector.canAITarget().and(Entity::isInteractable)); ++ ++ double distance = 0.0D; ++ MovingObjectPositionEntity result = null; ++ ++ for (Entity entity : entityList) { ++ AxisAlignedBB aabb = entity.getBoundingBox().grow((double) entity.getCollisionBorderSize()); ++ Optional rayTraceResult = aabb.calculateIntercept(start, end); ++ ++ if (rayTraceResult.isPresent()) { ++ Vec3D rayTrace = rayTraceResult.get(); ++ double distanceTo = start.distanceSquared(rayTrace); ++ if (distanceTo < distance || distance == 0.0D) { ++ result = new MovingObjectPositionEntity(entity, rayTrace); ++ distance = distanceTo; ++ } ++ } ++ } ++ ++ return result; ++ } ++ + public int shieldBlockingDelay = world.paperConfig.shieldBlockingDelay; + + public int getShieldBlockingDelay() { +diff --git a/src/main/java/net/minecraft/world/entity/IEntitySelector.java b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +index 4776a47566aac487dc77fd6b4b9b42b95974e31a..cb5cda5e6497edeb801ef712f9bd8823cb055750 100644 +--- a/src/main/java/net/minecraft/world/entity/IEntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +@@ -22,6 +22,7 @@ public final class IEntitySelector { + public static final Predicate e = (entity) -> { + return !(entity instanceof EntityHuman) || !entity.isSpectator() && !((EntityHuman) entity).isCreative(); + }; ++ public static Predicate canAITarget() { return f; } // Paper - OBFHELPER + public static final Predicate f = (entity) -> { + return !(entity instanceof EntityHuman) || !entity.isSpectator() && !((EntityHuman) entity).isCreative() && entity.world.getDifficulty() != EnumDifficulty.PEACEFUL; + }; +diff --git a/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java b/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java +index 3941dd33da4b5c09d0087143f1d8a2d76fc18792..62513c812b497bb9d8dafe1d9c2f574059aebf15 100644 +--- a/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java ++++ b/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java +@@ -116,6 +116,7 @@ public class AxisAlignedBB { + return this.b(vec3d.x, vec3d.y, vec3d.z); + } + ++ public final AxisAlignedBB expand(double x, double y, double z) { return b(x, y, z); } // Paper - OBFHELPER + public AxisAlignedBB b(double d0, double d1, double d2) { + double d3 = this.minX; + double d4 = this.minY; +@@ -145,6 +146,12 @@ public class AxisAlignedBB { + return new AxisAlignedBB(d3, d4, d5, d6, d7, d8); + } + ++ // Paper start ++ public AxisAlignedBB grow(double d0) { ++ return grow(d0, d0, d0); ++ } ++ // Paper end ++ + public AxisAlignedBB grow(double d0, double d1, double d2) { + double d3 = this.minX - d0; + double d4 = this.minY - d1; +@@ -204,6 +211,7 @@ public class AxisAlignedBB { + return this.minX < d3 && this.maxX > d0 && this.minY < d4 && this.maxY > d1 && this.minZ < d5 && this.maxZ > d2; + } + ++ public final boolean contains(Vec3D vec3d) { return d(vec3d); } // Paper - OBFHELPER + public boolean d(Vec3D vec3d) { + return this.e(vec3d.x, vec3d.y, vec3d.z); + } +@@ -237,6 +245,7 @@ public class AxisAlignedBB { + return this.g(-d0); + } + ++ public final Optional calculateIntercept(Vec3D vec3d, Vec3D vec3d1) { return b(vec3d, vec3d1); } // Paper - OBFHELPER + public Optional b(Vec3D vec3d, Vec3D vec3d1) { + double[] adouble = new double[]{1.0D}; + double d0 = vec3d1.x - vec3d.x; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index c692626b747008a5418ecabf550fc67e3b676f5b..ff586b8366a6298f1906551b068e8abb26fcabc7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1,6 +1,7 @@ + package org.bukkit.craftbukkit.entity; + + import com.destroystokyo.paper.block.TargetBlockInfo; ++import com.destroystokyo.paper.entity.TargetEntityInfo; + import com.google.common.base.Preconditions; + import com.google.common.collect.Sets; + import java.util.ArrayList; +@@ -42,8 +43,11 @@ import net.minecraft.world.entity.projectile.EntityThrownExpBottle; + import net.minecraft.world.entity.projectile.EntityThrownTrident; + import net.minecraft.world.entity.projectile.EntityTippedArrow; + import net.minecraft.world.entity.projectile.EntityWitherSkull; ++import net.minecraft.world.level.RayTrace; + import net.minecraft.world.phys.MovingObjectPosition; + import net.minecraft.world.phys.MovingObjectPositionBlock; ++import net.minecraft.world.phys.MovingObjectPositionEntity; ++import net.minecraft.world.phys.Vec3D; + import org.apache.commons.lang.Validate; + import org.bukkit.FluidCollisionMode; + import org.bukkit.Location; +@@ -227,6 +231,33 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + new TargetBlockInfo(CraftBlock.at(getHandle().world, ((MovingObjectPositionBlock)rayTrace).getBlockPosition()), + MCUtil.toBukkitBlockFace(((MovingObjectPositionBlock)rayTrace).getDirection())); + } ++ ++ public Entity getTargetEntity(int maxDistance, boolean ignoreBlocks) { ++ MovingObjectPositionEntity rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); ++ return rayTrace == null ? null : rayTrace.getEntity().getBukkitEntity(); ++ } ++ ++ public TargetEntityInfo getTargetEntityInfo(int maxDistance, boolean ignoreBlocks) { ++ MovingObjectPositionEntity rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); ++ return rayTrace == null ? null : new TargetEntityInfo(rayTrace.getEntity().getBukkitEntity(), new org.bukkit.util.Vector(rayTrace.getPos().x, rayTrace.getPos().y, rayTrace.getPos().z)); ++ } ++ ++ public MovingObjectPositionEntity rayTraceEntity(int maxDistance, boolean ignoreBlocks) { ++ MovingObjectPositionEntity rayTrace = getHandle().getTargetEntity(maxDistance); ++ if (rayTrace == null) { ++ return null; ++ } ++ if (!ignoreBlocks) { ++ MovingObjectPosition rayTraceBlocks = getHandle().getRayTrace(maxDistance, RayTrace.FluidCollisionOption.NONE); ++ if (rayTraceBlocks != null) { ++ Vec3D eye = getHandle().getEyePosition(1.0F); ++ if (eye.distanceSquared(rayTraceBlocks.getPos()) <= eye.distanceSquared(rayTrace.getPos())) { ++ return null; ++ } ++ } ++ } ++ return rayTrace; ++ } + // Paper end + + @Override diff --git a/patches/server-unmapped/0001/0337-Use-proper-max-length-when-serialising-BungeeCord-te.patch b/patches/server-unmapped/0001/0337-Use-proper-max-length-when-serialising-BungeeCord-te.patch new file mode 100644 index 0000000000..b9eefdc882 --- /dev/null +++ b/patches/server-unmapped/0001/0337-Use-proper-max-length-when-serialising-BungeeCord-te.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Wed, 20 Mar 2019 21:19:29 -0700 +Subject: [PATCH] Use proper max length when serialising BungeeCord text + component + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java +index f6a1c5ac9acb34b1ef2262721adbbb1a5b0feaf7..fefcacf27d71c67403555502685a992a5a706099 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java +@@ -9,7 +9,7 @@ import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.network.protocol.Packet; + + public class PacketPlayOutChat implements Packet { +- ++ private static final int MAX_LENGTH = Short.MAX_VALUE * 8 + 8; // Paper + private IChatBaseComponent a; + public net.kyori.adventure.text.Component adventure$message; // Paper + public net.md_5.bungee.api.chat.BaseComponent[] components; // Spigot +@@ -43,9 +43,9 @@ public class PacketPlayOutChat implements Packet { + //packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(components)); // Paper - comment, replaced with below + // Paper start - don't nest if we don't need to so that we can preserve formatting + if (this.components.length == 1) { +- packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(this.components[0])); ++ packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(this.components[0]), MAX_LENGTH); // Paper - use proper max length + } else { +- packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(this.components)); ++ packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(this.components), MAX_LENGTH); // Paper - use proper max length + } + // Paper end + } else { diff --git a/patches/server-unmapped/0001/0338-Entity-getEntitySpawnReason.patch b/patches/server-unmapped/0001/0338-Entity-getEntitySpawnReason.patch new file mode 100644 index 0000000000..4523ff9267 --- /dev/null +++ b/patches/server-unmapped/0001/0338-Entity-getEntitySpawnReason.patch @@ -0,0 +1,121 @@ +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/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 0c1867c00be9ecda5294298c5b9d22098e213a81..b8a32742b2b2558a6155fc72e277a693c2306302 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1041,6 +1041,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + // CraftBukkit start + private boolean addEntity0(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { + org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper + // Paper start + if (entity.valid) { + MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index fa288b099b17adafc085fb0fc5da6e810d078952..33b5825d753029e98ea7a11a4758280eddd2584c 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -335,7 +335,7 @@ public abstract class PlayerList { + // CraftBukkit start + WorldServer finalWorldServer = worldserver1; + Entity entity = EntityTypes.a(nbttagcompound1.getCompound("Entity"), finalWorldServer, (entity1) -> { +- return !finalWorldServer.addEntitySerialized(entity1) ? null : entity1; ++ return !finalWorldServer.addEntitySerialized(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; // Paper + // 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 e9b535622d6c33083c575ee4691598014dba0e2c..cbdd75feb7250e771111184b1fac7c4a6bf6e575 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -63,6 +63,8 @@ import net.minecraft.world.EnumHand; + import net.minecraft.world.EnumInteractionResult; + import net.minecraft.world.INamableTileEntity; + import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.entity.animal.EntityAnimal; ++import net.minecraft.world.entity.animal.EntityFish; + import net.minecraft.world.entity.item.EntityItem; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.entity.vehicle.EntityBoat; +@@ -158,6 +160,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + }; + public List entitySlice = null; ++ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; + // Paper end + + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper +@@ -1673,6 +1676,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + if (this.origin != null) { + nbttagcompound.set("Paper.Origin", this.createList(origin.getX(), origin.getY(), origin.getZ())); + } ++ if (spawnReason != null) { ++ nbttagcompound.setString("Paper.SpawnReason", spawnReason.name()); ++ } + // Save entity's from mob spawner status + if (spawnedViaMobSpawner) { + nbttagcompound.setBoolean("Paper.FromMobSpawner", true); +@@ -1807,6 +1813,26 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + spawnedViaMobSpawner = nbttagcompound.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status ++ if (nbttagcompound.hasKey("Paper.SpawnReason")) { ++ String spawnReasonName = nbttagcompound.getString("Paper.SpawnReason"); ++ try { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName); ++ } catch (Exception ignored) { ++ LogManager.getLogger().error("Unknown SpawnReason " + spawnReasonName + " for " + this); ++ } ++ } ++ if (spawnReason == null) { ++ if (spawnedViaMobSpawner) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; ++ } else if (this instanceof EntityInsentient && (this instanceof EntityAnimal || this instanceof EntityFish) && !((EntityInsentient) this).isTypeNotPersistent(0.0)) { ++ if (!nbttagcompound.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/MobSpawnerAbstract.java b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +index 867478484c0ba4ff467b96e458689937299b981d..34bcee4ff55ba118ba393e94b3c25ee2b84feaa2 100644 +--- a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java ++++ b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +@@ -183,6 +183,7 @@ public abstract class MobSpawnerAbstract { + // Spigot End + } + entity.spawnedViaMobSpawner = true; // Paper ++ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper + flag = true; // Paper + // Spigot Start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, blockposition).isCancelled()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 3642b17cafffd2818ee7a18d26bc25645f596115..93bbf63e9d38f32d5528c7693633d4b65655bb9d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1106,5 +1106,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-unmapped/0001/0339-Update-entity-Metadata-for-all-tracked-players.patch b/patches/server-unmapped/0001/0339-Update-entity-Metadata-for-all-tracked-players.patch new file mode 100644 index 0000000000..f05b82653c --- /dev/null +++ b/patches/server-unmapped/0001/0339-Update-entity-Metadata-for-all-tracked-players.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AgentTroll +Date: Fri, 22 Mar 2019 22:24:03 -0700 +Subject: [PATCH] Update entity Metadata for all tracked players + + +diff --git a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java +index e3cdea3c85d762af6984f3dbe544fdfe101f6ff6..6110d7723b70df5380338a42b5cbff3446294bac 100644 +--- a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java ++++ b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java +@@ -421,6 +421,12 @@ public class EntityTrackerEntry { + return PacketPlayOutEntity.a(this.xLoc, this.yLoc, this.zLoc); + } + ++ // Paper start - Add broadcast method ++ void broadcast(Packet packet) { ++ this.getPacketConsumer().accept(packet); ++ } ++ // Paper end ++ + private void broadcastIncludingSelf(Packet packet) { + this.f.accept(packet); + if (this.tracker instanceof EntityPlayer) { +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 2c949e079e5de86c902b162dfdb6adffe0511448..02ae07db49d3793c0cc40ebe55cdc6b383f9b2e3 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -2287,7 +2287,14 @@ public class PlayerConnection implements PacketListenerPlayIn { + + if (event.isCancelled() || this.player.inventory.getItemInHand() == null || this.player.inventory.getItemInHand().getItem() != origItem) { + // Refresh the current entity metadata +- this.sendPacket(new PacketPlayOutEntityMetadata(entity.getId(), entity.getDataWatcher(), true)); ++ // Paper start - update entity for all players ++ PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entity.getId(), entity.getDataWatcher(), true); ++ if (entity.tracker != null) { ++ entity.tracker.broadcast(packet); ++ } else { ++ this.sendPacket(packet); ++ } ++ // Paper end + } + + if (event.isCancelled()) { diff --git a/patches/server-unmapped/0001/0340-Implement-PlayerPostRespawnEvent.patch b/patches/server-unmapped/0001/0340-Implement-PlayerPostRespawnEvent.patch new file mode 100644 index 0000000000..fa19b6b3bb --- /dev/null +++ b/patches/server-unmapped/0001/0340-Implement-PlayerPostRespawnEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MisterVector +Date: Fri, 26 Oct 2018 21:31:00 -0700 +Subject: [PATCH] Implement PlayerPostRespawnEvent + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 33b5825d753029e98ea7a11a4758280eddd2584c..e029439803df82354de47cd6f047e98578ae9f95 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -741,9 +741,14 @@ public abstract class PlayerList { + // this.a(entityplayer1, entityplayer, worldserver1); // CraftBukkit - removed + boolean flag2 = false; + ++ // Paper start ++ boolean isBedSpawn = false; ++ boolean isRespawn = false; ++ // Paper end ++ + // CraftBukkit start - fire PlayerRespawnEvent + if (location == null) { +- boolean isBedSpawn = false; ++ // boolean isBedSpawn = false; // Paper - moved up + WorldServer worldserver1 = this.server.getWorldServer(entityplayer.getSpawnDimension()); + if (worldserver1 != null) { + Optional optional; +@@ -794,6 +799,7 @@ public abstract class PlayerList { + + location = respawnEvent.getRespawnLocation(); + if (!flag) entityplayer.reset(); // SPIGOT-4785 ++ isRespawn = true; // Paper + } else { + location.setWorld(worldserver.getWorld()); + } +@@ -851,6 +857,13 @@ public abstract class PlayerList { + if (entityplayer.playerConnection.isDisconnected()) { + this.savePlayerFile(entityplayer); + } ++ ++ // Paper start ++ if (isRespawn) { ++ cserver.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerPostRespawnEvent(entityplayer.getBukkitEntity(), location, isBedSpawn)); ++ } ++ // Paper end ++ + // CraftBukkit end + return entityplayer1; + } diff --git a/patches/server-unmapped/0001/0341-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch b/patches/server-unmapped/0001/0341-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch new file mode 100644 index 0000000000..f77a4f7db2 --- /dev/null +++ b/patches/server-unmapped/0001/0341-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 24 Mar 2019 18:09:20 -0400 +Subject: [PATCH] don't go below 0 for pickupDelay, breaks picking up items + +vanilla checks for == 0 + +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityItem.java b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +index 4cfe3475fa913cd46116f13ea8ed9caf5372a41a..02e4d6891adc902f73ed349f15dae3a429bd283a 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityItem.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +@@ -87,6 +87,7 @@ public class EntityItem extends Entity { + // CraftBukkit start - Use wall time for pickup and despawn timers + int elapsedTicks = MinecraftServer.currentTick - this.lastTick; + if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks; ++ this.pickupDelay = Math.max(0, this.pickupDelay); // Paper - don't go below 0 + if (this.age != -32768) this.age += elapsedTicks; + this.lastTick = MinecraftServer.currentTick; + // CraftBukkit end +@@ -179,6 +180,7 @@ public class EntityItem extends Entity { + // CraftBukkit start - Use wall time for pickup and despawn timers + int elapsedTicks = MinecraftServer.currentTick - this.lastTick; + if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks; ++ this.pickupDelay = Math.max(0, this.pickupDelay); // Paper - don't go below 0 + if (this.age != -32768) this.age += elapsedTicks; + this.lastTick = MinecraftServer.currentTick; + // CraftBukkit end diff --git a/patches/server-unmapped/0001/0342-Server-Tick-Events.patch b/patches/server-unmapped/0001/0342-Server-Tick-Events.patch new file mode 100644 index 0000000000..ca5f165d5a --- /dev/null +++ b/patches/server-unmapped/0001/0342-Server-Tick-Events.patch @@ -0,0 +1,32 @@ +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 7dee17e52a5b23ba4d8089482b39215041a64579..602deb6b456de6cdefc480bf1aab959f9d60007d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1238,6 +1238,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +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 + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 050930ffd940c40fc057054f11b86a7042c8ec55..49e3205dbd94b06b9504039c1a93f830b025a8bb 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -694,6 +694,46 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + }); + } + ++ // Paper start - process inventory ++ 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 (EnchantmentManager.shouldNotDrop(item) || itemsToKeep.isEmpty() || item.isEmpty()) { ++ inv.set(i, ItemStack.NULL_ITEM); ++ 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.NULL_ITEM); ++ } ++ } ++ } ++ // Paper end ++ + @Override + public void die(DamageSource damagesource) { + boolean flag = this.world.getGameRules().getBoolean(GameRules.SHOW_DEATH_MESSAGES); +@@ -777,7 +817,12 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + 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.inventory.clear(); ++ // Paper start - replace logic ++ for (NonNullList inv : this.inventory.getComponents()) { ++ processKeep(event, inv); ++ } ++ processKeep(event, null); ++ // Paper end + } + + this.setSpectatorTarget(this); // Remove spectated target diff --git a/patches/server-unmapped/0001/0344-Optimize-Captured-TileEntity-Lookup.patch b/patches/server-unmapped/0001/0344-Optimize-Captured-TileEntity-Lookup.patch new file mode 100644 index 0000000000..4c52441273 --- /dev/null +++ b/patches/server-unmapped/0001/0344-Optimize-Captured-TileEntity-Lookup.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 6 Apr 2019 10:16:48 -0400 +Subject: [PATCH] Optimize Captured TileEntity Lookup + +upstream was doing a containsKey/get pattern, and always doing it at that. +that scenario is only even valid if were in the middle of a block place. + +Optimize to check if the captured list even has values in it, and also to +just do a get call since the value can never be null. + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 51cdd8a8609934f97dfc582377b691551fd4849b..0f986fd039923895de861e70d304646cc13764f3 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -968,12 +968,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return null; + } else { + // CraftBukkit start +- if (capturedTileEntities.containsKey(blockposition)) { +- return capturedTileEntities.get(blockposition); ++ TileEntity tileentity = null; // Paper ++ if (!capturedTileEntities.isEmpty() && (tileentity = capturedTileEntities.get(blockposition)) != null) { // Paper ++ return tileentity; // Paper + } + // CraftBukkit end + +- TileEntity tileentity = null; ++ //TileEntity tileentity = null; // Paper - move up + + if (this.tickingTileEntities) { + tileentity = this.E(blockposition); diff --git a/patches/server-unmapped/0001/0345-Add-Heightmap-API.patch b/patches/server-unmapped/0001/0345-Add-Heightmap-API.patch new file mode 100644 index 0000000000..42026bae99 --- /dev/null +++ b/patches/server-unmapped/0001/0345-Add-Heightmap-API.patch @@ -0,0 +1,55 @@ +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/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 0f986fd039923895de861e70d304646cc13764f3..2d507f524152eb92a495d49a7a45a19aec522657 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -670,8 +670,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + } + } + +- @Override +- public int a(HeightMap.Type heightmap_type, int i, int j) { ++ public final int getHighestBlockY(final HeightMap.Type heightmap, final int x, final int z) { return this.a(heightmap, x, z); } // Paper - OBFHELPER ++ @Override public int a(HeightMap.Type heightmap_type, int i, int j) { // Paper - OBFHELPER + int k; + + if (i >= -30000000 && j >= -30000000 && i < 30000000 && j < 30000000) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 2197dad881bf5e9d90902c4526db18e19a087230..ca63620f4b05ddc226b28791c2752609166f7e1c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -343,6 +343,29 @@ public class CraftWorld implements World { + return getHighestBlockYAt(x, z, org.bukkit.HeightMap.MOTION_BLOCKING); + } + ++ // 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.getHighestBlockY(net.minecraft.world.level.levelgen.HeightMap.Type.WORLD_SURFACE, x, z); ++ case SOLID: ++ return this.world.getHighestBlockY(net.minecraft.world.level.levelgen.HeightMap.Type.OCEAN_FLOOR, x, z); ++ case SOLID_OR_LIQUID: ++ return this.world.getHighestBlockY(net.minecraft.world.level.levelgen.HeightMap.Type.MOTION_BLOCKING, x, z); ++ case SOLID_OR_LIQUID_NO_LEAVES: ++ return this.world.getHighestBlockY(net.minecraft.world.level.levelgen.HeightMap.Type.MOTION_BLOCKING_NO_LEAVES, x, z); ++ default: ++ throw new UnsupportedOperationException(); ++ } ++ } ++ // Paper end ++ + @Override + public Location getSpawnLocation() { + BlockPosition spawn = world.getSpawn(); diff --git a/patches/server-unmapped/0001/0346-Mob-Spawner-API-Enhancements.patch b/patches/server-unmapped/0001/0346-Mob-Spawner-API-Enhancements.patch new file mode 100644 index 0000000000..37f6cfaac5 --- /dev/null +++ b/patches/server-unmapped/0001/0346-Mob-Spawner-API-Enhancements.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 19 Apr 2019 12:41:13 -0500 +Subject: [PATCH] Mob Spawner API Enhancements + + +diff --git a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +index 34bcee4ff55ba118ba393e94b3c25ee2b84feaa2..5538404456dfee42257fad9040fcc0fefdfc5fab 100644 +--- a/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java ++++ b/src/main/java/net/minecraft/world/level/MobSpawnerAbstract.java +@@ -65,6 +65,7 @@ public abstract class MobSpawnerAbstract { + this.mobs.clear(); // CraftBukkit - SPIGOT-3496, MC-92282 + } + ++ public boolean isActivated() { return h(); } // Paper - OBFHELPER + private boolean h() { + BlockPosition blockposition = this.b(); + +@@ -221,6 +222,7 @@ public abstract class MobSpawnerAbstract { + } + } + ++ public void resetTimer() { i(); } // Paper - OBFHELPER + private void i() { + if (this.maxSpawnDelay <= this.minSpawnDelay) { + this.spawnDelay = this.minSpawnDelay; +@@ -238,7 +240,13 @@ public abstract class MobSpawnerAbstract { + } + + public void a(NBTTagCompound nbttagcompound) { ++ // Paper start - use larger int if set ++ if (nbttagcompound.hasKey("Paper.Delay")) { ++ this.spawnDelay = nbttagcompound.getInt("Paper.Delay"); ++ } else { + this.spawnDelay = nbttagcompound.getShort("Delay"); ++ } ++ // Paper end + this.mobs.clear(); + if (nbttagcompound.hasKeyOfType("SpawnPotentials", 9)) { + NBTTagList nbttaglist = nbttagcompound.getList("SpawnPotentials", 10); +@@ -253,10 +261,15 @@ public abstract class MobSpawnerAbstract { + } else if (!this.mobs.isEmpty()) { + this.setSpawnData((MobSpawnerData) WeightedRandom.a(this.a().random, this.mobs)); + } +- ++ // Paper start - use ints if set ++ if (nbttagcompound.hasKeyOfType("Paper.MinSpawnDelay", 99)) { ++ this.minSpawnDelay = nbttagcompound.getInt("Paper.MinSpawnDelay"); ++ this.maxSpawnDelay = nbttagcompound.getInt("Paper.MaxSpawnDelay"); ++ this.spawnCount = nbttagcompound.getShort("SpawnCount"); ++ } else // Paper end + if (nbttagcompound.hasKeyOfType("MinSpawnDelay", 99)) { +- this.minSpawnDelay = nbttagcompound.getShort("MinSpawnDelay"); +- this.maxSpawnDelay = nbttagcompound.getShort("MaxSpawnDelay"); ++ this.minSpawnDelay = nbttagcompound.getInt("MinSpawnDelay"); ++ this.maxSpawnDelay = nbttagcompound.getInt("MaxSpawnDelay"); + this.spawnCount = nbttagcompound.getShort("SpawnCount"); + } + +@@ -281,9 +294,20 @@ public abstract class MobSpawnerAbstract { + if (minecraftkey == null) { + return nbttagcompound; + } else { +- nbttagcompound.setShort("Delay", (short) this.spawnDelay); +- nbttagcompound.setShort("MinSpawnDelay", (short) this.minSpawnDelay); +- nbttagcompound.setShort("MaxSpawnDelay", (short) this.maxSpawnDelay); ++ // Paper start ++ if (spawnDelay > Short.MAX_VALUE) { ++ nbttagcompound.setInt("Paper.Delay", this.spawnDelay); ++ } ++ nbttagcompound.setShort("Delay", (short) Math.min(Short.MAX_VALUE, this.spawnDelay)); ++ ++ if (minSpawnDelay > Short.MAX_VALUE || maxSpawnDelay > Short.MAX_VALUE) { ++ nbttagcompound.setInt("Paper.MinSpawnDelay", this.minSpawnDelay); ++ nbttagcompound.setInt("Paper.MaxSpawnDelay", this.maxSpawnDelay); ++ } ++ ++ nbttagcompound.setShort("MinSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.minSpawnDelay)); ++ nbttagcompound.setShort("MaxSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.maxSpawnDelay)); ++ // Paper end + nbttagcompound.setShort("SpawnCount", (short) this.spawnCount); + nbttagcompound.setShort("MaxNearbyEntities", (short) this.maxNearbyEntities); + nbttagcompound.setShort("RequiredPlayerRange", (short) this.requiredPlayerRange); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java +index 28295ebd338806a35cbef164cb014abfe7dae769..3d29be926e36b9a5a981eea1f2a1ec54a4c43393 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java +@@ -1,13 +1,21 @@ + package org.bukkit.craftbukkit.block; + + import com.google.common.base.Preconditions; ++import net.minecraft.core.IRegistry; ++import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.resources.MinecraftKey; + import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.level.MobSpawnerData; + import net.minecraft.world.level.block.entity.TileEntityMobSpawner; + import org.bukkit.Material; + import org.bukkit.block.Block; + import org.bukkit.block.CreatureSpawner; + import org.bukkit.entity.EntityType; ++// Paper start ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.inventory.ItemStack; ++// Paper end + + public class CraftCreatureSpawner extends CraftBlockEntityState implements CreatureSpawner { + +@@ -121,4 +129,30 @@ public class CraftCreatureSpawner extends CraftBlockEntityState +Date: Mon, 6 May 2019 01:29:25 -0400 +Subject: [PATCH] Per-Player View Distance API placeholders + +I hope to look at this more in-depth soon. It appears doable. +However this should not block the update. + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java +index 51993191e01f55e16667c25b8b57d6a6ddaf493b..5168a40eb53565bb3028efe559601acf72bddae5 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java +@@ -625,9 +625,10 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + if (this.deathAnimationTicks == 1 && !this.isSilent()) { + // CraftBukkit start - Use relative location for far away sounds + // this.world.b(1028, this.getChunkCoordinates(), 0); +- //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API ++ int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API + for (net.minecraft.server.level.EntityPlayer player : (List) ((WorldServer)world).getPlayers()) { +- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch ++ // final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch ++ // Paper end + double deltaX = this.locX() - player.locX(); + double deltaZ = this.locZ() - player.locZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java +index 145767e8b0fc4105a0afa47af17dcdbb75e952bc..174eb12722872182b2d9b54841e5bb57893695a1 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java +@@ -258,9 +258,9 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + if (!this.isSilent()) { + // CraftBukkit start - Use relative location for far away sounds + // this.world.b(1023, new BlockPosition(this), 0); +- //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API ++ int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API + for (EntityPlayer player : (List)this.world.getPlayers()) { +- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch ++ // final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch + double deltaX = this.locX() - player.locX(); + double deltaZ = this.locZ() - player.locZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index e645a3386df6334e99d80ec6961399461c9545a9..43e4ade73619d430be7ee93687e98ef5a27cb329 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2240,6 +2240,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + super.remove(); + } + } ++ ++ @Override ++ public int getViewDistance() { ++ throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ } ++ ++ @Override ++ public void setViewDistance(int viewDistance) { ++ throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ } + // Paper end + + // Spigot start diff --git a/patches/server-unmapped/0001/0348-Fix-CB-call-to-changed-postToMainThread-method.patch b/patches/server-unmapped/0001/0348-Fix-CB-call-to-changed-postToMainThread-method.patch new file mode 100644 index 0000000000..9d2ee3f627 --- /dev/null +++ b/patches/server-unmapped/0001/0348-Fix-CB-call-to-changed-postToMainThread-method.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Fri, 10 May 2019 18:38:19 +0100 +Subject: [PATCH] Fix CB call to changed postToMainThread method + + +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 02ae07db49d3793c0cc40ebe55cdc6b383f9b2e3..d84d87b0dd01b4d67e55f1b445928285c45a08ca 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -445,7 +445,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + + this.networkManager.getClass(); + // CraftBukkit - Don't wait +- minecraftserver.postToMainThread(networkmanager::handleDisconnection); ++ minecraftserver.scheduleOnMain(networkmanager::handleDisconnection); // Paper + } + + private void a(T t0, Consumer consumer, BiFunction>> bifunction) { diff --git a/patches/server-unmapped/0001/0349-Fix-sounds-when-item-frames-are-modified-MC-123450.patch b/patches/server-unmapped/0001/0349-Fix-sounds-when-item-frames-are-modified-MC-123450.patch new file mode 100644 index 0000000000..f14e3f7905 --- /dev/null +++ b/patches/server-unmapped/0001/0349-Fix-sounds-when-item-frames-are-modified-MC-123450.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sat, 27 Apr 2019 20:00:43 +0100 +Subject: [PATCH] Fix sounds when item frames are modified (MC-123450) + +This also fixes the adding sound playing when the item frame direction is changed. + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/EntityItemFrame.java +index 584f64946821092afab57b9409bc249403bc16e7..43152a6c70c9433d627a58051101530ddd693307 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/EntityItemFrame.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityItemFrame.java +@@ -277,7 +277,7 @@ public class EntityItemFrame extends EntityHanging { + } + + this.getDataWatcher().set(EntityItemFrame.ITEM, itemstack); +- if (!itemstack.isEmpty() && playSound) { // CraftBukkit ++ if (!itemstack.isEmpty() && flag && playSound) { // CraftBukkit // Paper - only play sound when update flag is set + this.playSound(SoundEffects.ENTITY_ITEM_FRAME_ADD_ITEM, 1.0F, 1.0F); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java +index 86bb26cd1d327056a64926ed6fc53142ad3756c2..cbdbb89ec28f9b6c3a0eb31be94c440254c6e266 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java +@@ -50,7 +50,7 @@ public class CraftItemFrame extends CraftHanging implements ItemFrame { + old.die(); + + EntityItemFrame frame = new EntityItemFrame(world, position, direction); +- frame.setItem(item); ++ frame.setItem(item, true, false); // Paper - fix itemframe sound + world.addEntity(frame); + this.entity = frame; + } diff --git a/patches/server-unmapped/0001/0350-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch b/patches/server-unmapped/0001/0350-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch new file mode 100644 index 0000000000..c47104795a --- /dev/null +++ b/patches/server-unmapped/0001/0350-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 13 May 2019 21:10:59 -0700 +Subject: [PATCH] Fix CraftServer#isPrimaryThread and MinecraftServer + isMainThread + +md_5 changed it so he could shut down the server asynchronously +from watchdog, although we have patches that prevent that type +of behavior for this exact reason. + +md_5 also placed code in PlayerConnectionUtils that would have +solved https://bugs.mojang.com/browse/MC-142590, making the change +to MinecraftServer#isMainThread irrelevant. +By reverting his change to MinecraftServer#isMainThread packet +handling that should have been handled synchronously will be handled +synchronously when the server gets shut down. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 602deb6b456de6cdefc480bf1aab959f9d60007d..5020b1ef1e0fc08a00a40aecfd3eeb74348865d1 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2191,7 +2191,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Fri, 28 Sep 2018 21:49:53 -0400 +Subject: [PATCH] Fix issues with entity loss due to unloaded chunks + +Vanilla has risk of losing entities by causing them to be +removed from all chunks if they try to move into an unloaded chunk. + +This pretty much means high chance this entity will be lost in this +scenario. + +There is another case that adding an entity to the world can fail if +the chunk isn't loaded. + +Lots of the server is designed around addEntity never expecting to fail +for these reasons, nor is it really logical. + +This change ensures the chunks are always loaded when entities are +added to the world, or a valid entity moves between chunks. + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index b8a32742b2b2558a6155fc72e277a693c2306302..61d3524e962b97ed032af1990f8dc6513fbe51d6 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -854,11 +854,18 @@ public class WorldServer extends World implements GeneratorAccessSeed { + int k = MathHelper.floor(entity.locZ() / 16.0D); + + if (!entity.inChunk || entity.chunkX != i || entity.chunkY != j || entity.chunkZ != k) { ++ // Paper start - remove entity if its in a chunk more correctly. ++ Chunk currentChunk = entity.getCurrentChunk(); ++ if (currentChunk != null) { ++ currentChunk.removeEntity(entity); ++ } ++ // Paper end ++ + if (entity.inChunk && this.isChunkLoaded(entity.chunkX, entity.chunkZ)) { + this.getChunkAt(entity.chunkX, entity.chunkZ).a(entity, entity.chunkY); + } + +- if (!entity.ck() && !this.isChunkLoaded(i, k)) { ++ if (!entity.valid && !entity.ck() && !this.isChunkLoaded(i, k)) { // Paper - always load chunks to register valid entities location + if (entity.inChunk) { + WorldServer.LOGGER.warn("Entity {} left loaded chunk area", entity); + } +@@ -1073,7 +1080,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + return false; + } + // CraftBukkit end +- IChunkAccess ichunkaccess = this.getChunkAt(MathHelper.floor(entity.locX() / 16.0D), MathHelper.floor(entity.locZ() / 16.0D), ChunkStatus.FULL, entity.attachedToPlayer); ++ IChunkAccess ichunkaccess = this.getChunkAt(MathHelper.floor(entity.locX() / 16.0D), MathHelper.floor(entity.locZ() / 16.0D), ChunkStatus.FULL, true); // Paper - always load chunks for entity adds + + if (!(ichunkaccess instanceof Chunk)) { + return false; diff --git a/patches/server-unmapped/0001/0352-Duplicate-UUID-Resolve-Option.patch b/patches/server-unmapped/0001/0352-Duplicate-UUID-Resolve-Option.patch new file mode 100644 index 0000000000..3bb620b1f5 --- /dev/null +++ b/patches/server-unmapped/0001/0352-Duplicate-UUID-Resolve-Option.patch @@ -0,0 +1,242 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index fbf3ccfb347a5ba6e895339e9576629d940d1aa4..38d25a12c6a52d8a83214e2a0f43a218cf15ceac 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -401,4 +401,43 @@ public class PaperWorldConfig { + private void preventMovingIntoUnloadedChunks() { + preventMovingIntoUnloadedChunks = getBoolean("prevent-moving-into-unloaded-chunks", false); + } ++ ++ public enum DuplicateUUIDMode { ++ SAFE_REGEN, DELETE, NOTHING, WARN ++ } ++ public DuplicateUUIDMode duplicateUUIDMode = DuplicateUUIDMode.SAFE_REGEN; ++ public int duplicateUUIDDeleteRange = 32; ++ private void repairDuplicateUUID() { ++ String desiredMode = getString("duplicate-uuid-resolver", "saferegen").toLowerCase().trim(); ++ duplicateUUIDDeleteRange = getInt("duplicate-uuid-saferegen-delete-range", duplicateUUIDDeleteRange); ++ switch (desiredMode.toLowerCase()) { ++ case "regen": ++ case "regenerate": ++ case "saferegen": ++ case "saferegenerate": ++ duplicateUUIDMode = DuplicateUUIDMode.SAFE_REGEN; ++ log("Duplicate UUID Resolve: Regenerate New UUID if distant (Delete likely duplicates within " + duplicateUUIDDeleteRange + " blocks)"); ++ break; ++ case "remove": ++ case "delete": ++ duplicateUUIDMode = DuplicateUUIDMode.DELETE; ++ log("Duplicate UUID Resolve: Delete Entity"); ++ break; ++ case "silent": ++ case "nothing": ++ duplicateUUIDMode = DuplicateUUIDMode.NOTHING; ++ logError("Duplicate UUID Resolve: Do Nothing (no logs) - Warning, may lose indication of bad things happening"); ++ break; ++ case "log": ++ case "warn": ++ duplicateUUIDMode = DuplicateUUIDMode.WARN; ++ log("Duplicate UUID Resolve: Warn (do nothing but log it happened, may be spammy)"); ++ break; ++ default: ++ duplicateUUIDMode = DuplicateUUIDMode.WARN; ++ logError("Warning: Invalid duplicate-uuid-resolver config " + desiredMode + " - must be one of: regen, delete, nothing, warn"); ++ log("Duplicate UUID Resolve: Warn (do nothing but log it happened, may be spammy)"); ++ break; ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index c4dd2bac48bb93117925b35dcd753d0fbb22e3cf..aeed11cfee42fbde2c2e5731f46ac24de6469e0e 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -1,6 +1,7 @@ + package net.minecraft.server.level; + + import co.aikar.timings.Timing; // Paper ++import com.destroystokyo.paper.PaperWorldConfig; // Paper + import com.google.common.collect.ImmutableList; + import com.google.common.collect.Iterables; + import com.google.common.collect.ComparisonChain; // Paper +@@ -23,14 +24,17 @@ import it.unimi.dsi.fastutil.objects.ObjectIterator; + import java.io.File; + import java.io.IOException; + import java.io.Writer; ++import java.util.HashMap; // Paper + import java.util.Collection; + import java.util.Iterator; + import java.util.List; ++import java.util.Map; // Paper + import java.util.Objects; + import java.util.Optional; + import java.util.Queue; + import java.util.Set; + import java.util.concurrent.CancellationException; ++import java.util.UUID; // Paper + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.CompletionException; + import java.util.concurrent.Executor; +@@ -73,6 +77,7 @@ import net.minecraft.world.entity.boss.EntityComplexPart; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.GameRules; ++import net.minecraft.world.level.World; + import net.minecraft.world.level.chunk.Chunk; + import net.minecraft.world.level.chunk.ChunkConverter; + import net.minecraft.world.level.chunk.ChunkGenerator; +@@ -697,12 +702,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities + boolean needsRemoval = false; + if (chunk.needsDecoration && !this.world.getServer().getServer().getSpawnNPCs() && entity instanceof net.minecraft.world.entity.npc.NPC) { +- entity.die(); ++ entity.dead = true; // Paper + needsRemoval = true; + } +- +- if (!(entity instanceof EntityHuman) && (needsRemoval || !this.world.addEntityChunk(entity))) { +- // CraftBukkit end ++ // CraftBukkit end ++ checkDupeUUID(entity); // Paper ++ if (!(entity instanceof EntityHuman) && (entity.dead || !this.world.addEntityChunk(entity))) { // Paper + if (list == null) { + list = Lists.newArrayList(new Entity[]{entity}); + } else { +@@ -729,6 +734,44 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + }); + } + ++ // Paper start ++ private void checkDupeUUID(Entity entity) { ++ PaperWorldConfig.DuplicateUUIDMode mode = world.paperConfig.duplicateUUIDMode; ++ if (mode != PaperWorldConfig.DuplicateUUIDMode.WARN ++ && mode != PaperWorldConfig.DuplicateUUIDMode.DELETE ++ && mode != PaperWorldConfig.DuplicateUUIDMode.SAFE_REGEN) { ++ return; ++ } ++ Entity other = world.getEntity(entity.getUniqueID()); ++ ++ if (mode == PaperWorldConfig.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.dead ++ && Objects.equals(other.getSaveID(), entity.getSaveID()) ++ && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < world.paperConfig.duplicateUUIDDeleteRange ++ ) { ++ if (World.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + " because it was near the duplicate and likely an actual duplicate. See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about."); ++ entity.dead = true; ++ return; ++ } ++ if (other != null && !other.dead) { ++ switch (mode) { ++ case SAFE_REGEN: { ++ entity.setUUID(UUID.randomUUID()); ++ if (World.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", regenerated UUID for " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about."); ++ break; ++ } ++ case DELETE: { ++ if (World.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about."); ++ entity.dead = true; ++ break; ++ } ++ default: ++ if (World.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", doing nothing to " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about."); ++ break; ++ } ++ } ++ } ++ // Paper end ++ + public CompletableFuture> a(PlayerChunk playerchunk) { + ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); + CompletableFuture, PlayerChunk.Failure>> completablefuture = this.a(chunkcoordintpair, 1, (i) -> { +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 61d3524e962b97ed032af1990f8dc6513fbe51d6..a7fbbb755b2e829022efb0ae63fc1020d5adda4f 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -4,6 +4,8 @@ import com.google.common.annotations.VisibleForTesting; + import com.google.common.collect.Iterables; + import co.aikar.timings.TimingHistory; // Paper + import co.aikar.timings.Timings; // Paper ++ ++import com.destroystokyo.paper.PaperWorldConfig; // Paper + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Queues; +@@ -1108,7 +1110,22 @@ public class WorldServer extends World implements GeneratorAccessSeed { + if (entity1 == null) { + return false; + } else { ++ // Paper start ++ if (entity1.dead) { ++ unregisterEntity(entity1); // remove the existing entity ++ return false; ++ } ++ // Paper end + WorldServer.LOGGER.warn("Trying to add entity with duplicated UUID {}. Existing {}#{}, new: {}#{}", uuid, EntityTypes.getName(entity1.getEntityType()), entity1.getId(), EntityTypes.getName(entity.getEntityType()), entity.getId()); // CraftBukkit // Paper ++ // Paper start ++ if (DEBUG_ENTITIES && entity.world.paperConfig.duplicateUUIDMode != PaperWorldConfig.DuplicateUUIDMode.NOTHING) { ++ if (entity1.addedToWorldStack != null) { ++ entity1.addedToWorldStack.printStackTrace(); ++ } ++ ++ getAddToWorldStackTrace(entity).printStackTrace(); ++ } ++ // Paper end + return true; + } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index cbdd75feb7250e771111184b1fac7c4a6bf6e575..5acf61ece9ca38a262387fd0395bd464312501fd 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2799,6 +2799,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + }); + } + ++ public final void setUUID(UUID uuid) { a_(uuid); } // Paper - OBFHELPER + public void a_(UUID uuid) { + this.uniqueID = uuid; + this.ae = this.uniqueID.toString(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index 79ff96f18c53f3d1ce4a00be2e2d8fe68f77bf54..3f926ed8e2b2c9dbf1e2493870af7eff3b6db019 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -542,6 +542,7 @@ public class Chunk implements IChunkAccess { + if (i != this.loc.x || j != this.loc.z) { + Chunk.LOGGER.warn("Wrong location! ({}, {}) should be ({}, {}), {}", i, j, this.loc.x, this.loc.z, entity); + entity.dead = true; ++ return; // Paper + } + + int k = MathHelper.floor(entity.locY() / 16.0D); diff --git a/patches/server-unmapped/0001/0353-improve-CraftWorld-isChunkLoaded.patch b/patches/server-unmapped/0001/0353-improve-CraftWorld-isChunkLoaded.patch new file mode 100644 index 0000000000..a5c0c7794e --- /dev/null +++ b/patches/server-unmapped/0001/0353-improve-CraftWorld-isChunkLoaded.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 21 May 2019 02:34:04 +0100 +Subject: [PATCH] improve CraftWorld#isChunkLoaded + +getChunkAt will request the chunk using vanillas chunk loading system, +which while we're not going to load the chunk, does involve the server +waiting for the execution queue to get to our request; We can just query +the chunk status and get a response now, vs having to wait + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index ca63620f4b05ddc226b28791c2752609166f7e1c..206289adb3294b7be15aaf54748c8da7e263bebb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -414,13 +414,13 @@ public class CraftWorld implements World { + + @Override + public boolean isChunkLoaded(int x, int z) { +- return world.getChunkProvider().isChunkLoaded(x, z); ++ return world.getChunkProvider().getChunkAtIfLoadedImmediately(x, z) != null; // Paper + } + + @Override + public boolean isChunkGenerated(int x, int z) { + try { +- return isChunkLoaded(x, z) || world.getChunkProvider().playerChunkMap.read(new ChunkCoordIntPair(x, z)) != null; ++ return world.getChunkProvider().getChunkAtIfCachedImmediately(x, z) != null || world.getChunkProvider().playerChunkMap.read(new ChunkCoordIntPair(x, z)) != null; // Paper (TODO check if the first part can be removed) + } catch (IOException ex) { + throw new RuntimeException(ex); + } diff --git a/patches/server-unmapped/0001/0354-Configurable-Keep-Spawn-Loaded-range-per-world.patch b/patches/server-unmapped/0001/0354-Configurable-Keep-Spawn-Loaded-range-per-world.patch new file mode 100644 index 0000000000..617218dc13 --- /dev/null +++ b/patches/server-unmapped/0001/0354-Configurable-Keep-Spawn-Loaded-range-per-world.patch @@ -0,0 +1,261 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 38d25a12c6a52d8a83214e2a0f43a218cf15ceac..ffe9b1a63d78925e1d77b9e730aef42fed6d58fa 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -440,4 +440,10 @@ public class PaperWorldConfig { + break; + } + } ++ ++ public short keepLoadedRange; ++ private void keepLoadedRange() { ++ keepLoadedRange = (short) (getInt("keep-spawn-loaded-range", Math.min(spigotConfig.viewDistance, 10)) * 16); ++ log( "Keep Spawn Loaded Range: " + (keepLoadedRange/16)); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 5020b1ef1e0fc08a00a40aecfd3eeb74348865d1..0aa723d9940f4b33cd587aef6e23e238c2c22359 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -717,35 +717,36 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant> 4).forEach(pair -> { ++ getChunkProvider().getChunkAtMainThread(pair.x, pair.z); ++ }); ++ } ++ public void removeTicketsForSpawn(int radiusInBlocks, BlockPosition spawn) { ++ // In order to respect vanilla behavior, which is ensuring everything but the spawn border can tick, we added tickets ++ // with level 31 for the non-border spawn chunks ++ ChunkProviderServer chunkproviderserver = this.getChunkProvider(); ++ int tickRadius = radiusInBlocks - 16; ++ ++ // remove ticking chunks ++ for (int x = -tickRadius; x <= tickRadius; x += 16) { ++ for (int z = -tickRadius; z <= tickRadius; z += 16) { ++ // radius of 2 will have the current chunk be level 31 ++ chunkproviderserver.removeTicket(TicketType.START, new ChunkCoordIntPair(spawn.add(x, 0, z)), 2, Unit.INSTANCE); ++ } ++ } ++ ++ // remove border chunks ++ ++ // remove border along x axis (including corner chunks) ++ for (int x = -radiusInBlocks; x <= radiusInBlocks; x += 16) { ++ // top ++ chunkproviderserver.removeTicket(TicketType.START, new ChunkCoordIntPair(spawn.add(x, 0, radiusInBlocks)), 1, Unit.INSTANCE); // level 32 ++ // bottom ++ chunkproviderserver.removeTicket(TicketType.START, new ChunkCoordIntPair(spawn.add(x, 0, -radiusInBlocks)), 1, Unit.INSTANCE); // level 32 ++ } ++ ++ // remove border along z axis (excluding corner chunks) ++ for (int z = -radiusInBlocks + 16; z < radiusInBlocks; z += 16) { ++ // right ++ chunkproviderserver.removeTicket(TicketType.START, new ChunkCoordIntPair(spawn.add(radiusInBlocks, 0, z)), 1, Unit.INSTANCE); // level 32 ++ // left ++ chunkproviderserver.removeTicket(TicketType.START, new ChunkCoordIntPair(spawn.add(-radiusInBlocks, 0, z)), 1, Unit.INSTANCE); // level 32 ++ } ++ } ++ // Paper end ++ + public void a(BlockPosition blockposition, float f) { +- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(new BlockPosition(this.worldData.a(), 0, this.worldData.c())); ++ // Paper - configurable spawn radius ++ BlockPosition prevSpawn = this.getSpawn(); ++ //ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(new BlockPosition(this.worldData.a(), 0, this.worldData.c())); + + this.worldData.setSpawn(blockposition, f); +- this.getChunkProvider().removeTicket(TicketType.START, chunkcoordintpair, 11, Unit.INSTANCE); +- this.getChunkProvider().addTicket(TicketType.START, new ChunkCoordIntPair(blockposition), 11, Unit.INSTANCE); ++ if (this.keepSpawnInMemory) { ++ // if this keepSpawnInMemory is false a plugin has already removed our tickets, do not re-add ++ this.removeTicketsForSpawn(this.paperConfig.keepLoadedRange, prevSpawn); ++ this.addTicketsForSpawn(this.paperConfig.keepLoadedRange, blockposition); ++ } + this.getMinecraftServer().getPlayerList().sendAll(new PacketPlayOutSpawnPosition(blockposition, f)); + } + +diff --git a/src/main/java/net/minecraft/server/level/progress/WorldLoadListener.java b/src/main/java/net/minecraft/server/level/progress/WorldLoadListener.java +index de011b5e3a5e751160b4d3b65b50f28e6c6a5f52..4d9c167a41366779dbfb5ded6ea0115ffbf06ed7 100644 +--- a/src/main/java/net/minecraft/server/level/progress/WorldLoadListener.java ++++ b/src/main/java/net/minecraft/server/level/progress/WorldLoadListener.java +@@ -11,4 +11,6 @@ public interface WorldLoadListener { + void a(ChunkCoordIntPair chunkcoordintpair, @Nullable ChunkStatus chunkstatus); + + void b(); ++ ++ void setChunkRadius(int radius); // Paper - allow changing chunk radius + } +diff --git a/src/main/java/net/minecraft/server/level/progress/WorldLoadListenerLogger.java b/src/main/java/net/minecraft/server/level/progress/WorldLoadListenerLogger.java +index 872d00de41533ab7f4b43874de6c1747022e2ac5..ca81664d884e80e5cb1eb376a2c2ef1e017f16c0 100644 +--- a/src/main/java/net/minecraft/server/level/progress/WorldLoadListenerLogger.java ++++ b/src/main/java/net/minecraft/server/level/progress/WorldLoadListenerLogger.java +@@ -12,16 +12,24 @@ import org.apache.logging.log4j.Logger; + public class WorldLoadListenerLogger implements WorldLoadListener { + + private static final Logger LOGGER = LogManager.getLogger(); +- private final int b; ++ private int b; // Paper - remove final + private int c; + private long d; + private long e = Long.MAX_VALUE; + + public WorldLoadListenerLogger(int i) { +- int j = i * 2 + 1; ++ // Paper start - Allow changing radius later for configurable spawn patch ++ this.setChunkRadius(i); // Move to method ++ } ++ ++ @Override ++ public void setChunkRadius(int radius) { ++ // Paper - copied from above ++ int j = radius * 2 + 1; + + this.b = j * j; + } ++ // Paper end + + @Override + public void a(ChunkCoordIntPair chunkcoordintpair) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 206289adb3294b7be15aaf54748c8da7e263bebb..dd9fe7d04666ff6173f9b39e6450bb1165cc9e8a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1972,15 +1972,21 @@ public class CraftWorld implements World { + + @Override + public void setKeepSpawnInMemory(boolean keepLoaded) { ++ // Paper start - Configurable spawn radius ++ if (keepLoaded == world.keepSpawnInMemory) { ++ // do nothing, nothing has changed ++ return; ++ } + world.keepSpawnInMemory = keepLoaded; + // Grab the worlds spawn chunk +- BlockPosition chunkcoordinates = this.world.getSpawn(); ++ BlockPosition prevSpawn = this.world.getSpawn(); + if (keepLoaded) { +- world.getChunkProvider().addTicket(TicketType.START, new ChunkCoordIntPair(chunkcoordinates), 11, Unit.INSTANCE); ++ world.addTicketsForSpawn(world.paperConfig.keepLoadedRange, prevSpawn); + } else { +- // TODO: doesn't work well if spawn changed.... +- world.getChunkProvider().removeTicket(TicketType.START, new ChunkCoordIntPair(chunkcoordinates), 11, Unit.INSTANCE); ++ // TODO: doesn't work well if spawn changed.... // paper - resolved ++ world.removeTicketsForSpawn(world.paperConfig.keepLoadedRange, prevSpawn); + } ++ // Paper end + } + + @Override diff --git a/patches/server-unmapped/0001/0355-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch b/patches/server-unmapped/0001/0355-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch new file mode 100644 index 0000000000..be4c0838f1 --- /dev/null +++ b/patches/server-unmapped/0001/0355-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 27 May 2019 17:35:39 -0500 +Subject: [PATCH] MC-114618 - Fix EntityAreaEffectCloud from going negative + size + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityAreaEffectCloud.java b/src/main/java/net/minecraft/world/entity/EntityAreaEffectCloud.java +index 47099ecd70e5077cad8372446d54e28398785bec..679dfe75ea68e38679cd7d6348d0e24ca61911e4 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityAreaEffectCloud.java ++++ b/src/main/java/net/minecraft/world/entity/EntityAreaEffectCloud.java +@@ -196,6 +196,12 @@ public class EntityAreaEffectCloud extends Entity { + super.tick(); + boolean flag = this.k(); + float f = this.getRadius(); ++ // Paper start - fix MC-114618 ++ if (f < 0.0F) { ++ this.die(); ++ return; ++ } ++ // Paper end + + if (this.world.isClientSide) { + ParticleParam particleparam = this.getParticle(); diff --git a/patches/server-unmapped/0001/0356-ChunkMapDistance-CME.patch b/patches/server-unmapped/0001/0356-ChunkMapDistance-CME.patch new file mode 100644 index 0000000000..148847522f --- /dev/null +++ b/patches/server-unmapped/0001/0356-ChunkMapDistance-CME.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 29 May 2019 04:01:22 +0100 +Subject: [PATCH] ChunkMapDistance CME + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +index 6ef404ee29ddc79aeca534a58ec182e0e8b1b6c8..961257ebc28a8b4753faf3c2d5b6abaea4ffc0dd 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +@@ -39,7 +39,16 @@ public abstract class ChunkMapDistance { + private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a(); + private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); + private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); +- private final Set pendingChunkUpdates = Sets.newHashSet(); ++ // Paper start use a queue, but still keep unique requirement ++ public final java.util.Queue pendingChunkUpdates = new java.util.ArrayDeque() { ++ @Override ++ public boolean add(PlayerChunk o) { ++ if (o.isUpdateQueued) return true; ++ o.isUpdateQueued = true; ++ return super.add(o); ++ } ++ }; ++ // Paper end + private final ChunkTaskQueueSorter i; + private final Mailbox> j; + private final Mailbox k; +@@ -100,26 +109,14 @@ public abstract class ChunkMapDistance { + ; + } + ++ // Paper start + if (!this.pendingChunkUpdates.isEmpty()) { +- // CraftBukkit start +- // Iterate pending chunk updates with protection against concurrent modification exceptions +- java.util.Iterator iter = this.pendingChunkUpdates.iterator(); +- int expectedSize = this.pendingChunkUpdates.size(); +- do { +- PlayerChunk playerchunk = iter.next(); +- iter.remove(); +- expectedSize--; +- +- playerchunk.a(playerchunkmap); +- +- // Reset iterator if set was modified using add() +- if (this.pendingChunkUpdates.size() != expectedSize) { +- expectedSize = this.pendingChunkUpdates.size(); +- iter = this.pendingChunkUpdates.iterator(); +- } +- } while (iter.hasNext()); +- // CraftBukkit end +- ++ while(!this.pendingChunkUpdates.isEmpty()) { ++ PlayerChunk remove = this.pendingChunkUpdates.remove(); ++ remove.isUpdateQueued = false; ++ remove.a(playerchunkmap); ++ } ++ // Paper end + return true; + } else { + if (!this.l.isEmpty()) { +@@ -373,7 +370,7 @@ public abstract class ChunkMapDistance { + ObjectIterator objectiterator = this.a.long2ByteEntrySet().iterator(); + + while (objectiterator.hasNext()) { +- it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry it_unimi_dsi_fastutil_longs_long2bytemap_entry = (it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry) objectiterator.next(); ++ Long2ByteMap.Entry it_unimi_dsi_fastutil_longs_long2bytemap_entry = (Long2ByteMap.Entry) objectiterator.next(); // Paper - decompile fix + byte b0 = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getByteValue(); + long j = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getLongKey(); + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 7dea5e783ce2a1f8ddd2b3ab7a19e03a56c36ba1..2c3d9a5d118cc4f3b5e78daf943911bb7386488a 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -45,6 +45,7 @@ public class PlayerChunk { + private static final CompletableFuture> UNLOADED_CHUNK_FUTURE = CompletableFuture.completedFuture(PlayerChunk.UNLOADED_CHUNK); + private static final List CHUNK_STATUSES = ChunkStatus.a(); + private static final PlayerChunk.State[] CHUNK_STATES = PlayerChunk.State.values(); ++ boolean isUpdateQueued = false; // Paper + private final AtomicReferenceArray>> statusFutures; + private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> tickingFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage diff --git a/patches/server-unmapped/0001/0357-Implement-CraftBlockSoundGroup.patch b/patches/server-unmapped/0001/0357-Implement-CraftBlockSoundGroup.patch new file mode 100644 index 0000000000..722e63e63a --- /dev/null +++ b/patches/server-unmapped/0001/0357-Implement-CraftBlockSoundGroup.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: simpleauthority +Date: Tue, 28 May 2019 03:48:51 -0700 +Subject: [PATCH] Implement CraftBlockSoundGroup + + +diff --git a/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java b/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..34b3b858f616c4a1f877e6e58d315de2d8ff0720 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java +@@ -0,0 +1,38 @@ ++package com.destroystokyo.paper.block; ++ ++import net.minecraft.world.level.block.SoundEffectType; ++import org.bukkit.Sound; ++import org.bukkit.craftbukkit.CraftSound; ++ ++public class CraftBlockSoundGroup implements BlockSoundGroup { ++ private final SoundEffectType soundEffectType; ++ ++ public CraftBlockSoundGroup(SoundEffectType soundEffectType) { ++ this.soundEffectType = soundEffectType; ++ } ++ ++ @Override ++ public Sound getBreakSound() { ++ return CraftSound.getBukkit(soundEffectType.getBreakSound()); ++ } ++ ++ @Override ++ public Sound getStepSound() { ++ return CraftSound.getBukkit(soundEffectType.getStepSound()); ++ } ++ ++ @Override ++ public Sound getPlaceSound() { ++ return CraftSound.getBukkit(soundEffectType.getPlaceSound()); ++ } ++ ++ @Override ++ public Sound getHitSound() { ++ return CraftSound.getBukkit(soundEffectType.getHitSound()); ++ } ++ ++ @Override ++ public Sound getFallSound() { ++ return CraftSound.getBukkit(soundEffectType.getFallSound()); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/SoundEffectType.java b/src/main/java/net/minecraft/world/level/block/SoundEffectType.java +index 0987b25ac586d5d7b7954256c740fdf736498dae..b2a52c6bad5a83f34188b8f3db18c61ff9f52869 100644 +--- a/src/main/java/net/minecraft/world/level/block/SoundEffectType.java ++++ b/src/main/java/net/minecraft/world/level/block/SoundEffectType.java +@@ -54,10 +54,10 @@ public class SoundEffectType { + public static final SoundEffectType U = new SoundEffectType(1.0F, 1.0F, SoundEffects.BLOCK_GILDED_BLACKSTONE_BREAK, SoundEffects.BLOCK_GILDED_BLACKSTONE_STEP, SoundEffects.BLOCK_GILDED_BLACKSTONE_PLACE, SoundEffects.BLOCK_GILDED_BLACKSTONE_HIT, SoundEffects.BLOCK_GILDED_BLACKSTONE_FALL); + public final float volume; + public final float pitch; +- public final SoundEffect breakSound; ++ public final SoundEffect breakSound; public final SoundEffect getBreakSound() { return this.breakSound; } // Paper - OBFHELPER // PAIL private -> public, rename breakSound + private final SoundEffect stepSound; + private final SoundEffect placeSound; +- public final SoundEffect hitSound; ++ public final SoundEffect hitSound; public final SoundEffect getHitSound() { return this.hitSound; } // Paper - OBFHELPER // PAIL private -> public, rename hitSound + private final SoundEffect fallSound; + + public SoundEffectType(float f, float f1, SoundEffect soundeffect, SoundEffect soundeffect1, SoundEffect soundeffect2, SoundEffect soundeffect3, SoundEffect soundeffect4) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 8a6d8f21937ce7e2ac4623a3083421ed5ef9aa63..724b230259b1b44bc9fdde6c4fcbcdde5f690e05 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -750,4 +750,11 @@ public class CraftBlock implements Block { + AxisAlignedBB aabb = shape.getBoundingBox(); + return new BoundingBox(getX() + aabb.minX, getY() + aabb.minY, getZ() + aabb.minZ, getX() + aabb.maxX, getY() + aabb.maxY, getZ() + aabb.maxZ); + } ++ ++ // Paper start ++ @Override ++ public com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup() { ++ return new com.destroystokyo.paper.block.CraftBlockSoundGroup(getNMSBlock().getBlockData().getStepSound()); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0358-Chunk-debug-command.patch b/patches/server-unmapped/0001/0358-Chunk-debug-command.patch new file mode 100644 index 0000000000..04f88209f2 --- /dev/null +++ b/patches/server-unmapped/0001/0358-Chunk-debug-command.patch @@ -0,0 +1,486 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 1 Jun 2019 13:00:55 -0700 +Subject: [PATCH] Chunk debug command + +Prints all chunk information to a text file into the debug +folder in the root server folder. The format is in JSON, and +the data format is described in MCUtil#dumpChunks(File) + +The command will output server version and all online players to the +file as well. We do not log anything but the location, world and +username of the player. + +Also logs the value of these config values (note not all are paper's): +- keep spawn loaded value +- spawn radius +- view distance + +Each chunk has the following logged: +- Coordinate +- Ticket level & its corresponding state +- Whether it is queued for unload +- Chunk status (may be unloaded) +- All tickets on the chunk + +Example log: +https://gist.githubusercontent.com/Spottedleaf/0131e7710ffd5d531e5fd246c3367380/raw/169ae1b2e240485f99bc7a6bd8e78d90e3af7397/chunks-2019-06-01_19.57.05.txt + +For references on certain keywords (ticket, status, etc), please see: + +https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528273&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528273 +https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528577&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528577 + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index b67bd98cca4a06bc0ebaed577195dffc3b3251ec..a7a02072e5c7ce62cbecbb638fcc74abf2fb57ee 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -9,10 +9,12 @@ import com.google.common.collect.Maps; + import net.minecraft.resources.MinecraftKey; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkProviderServer; ++import net.minecraft.server.level.PlayerChunk; + import net.minecraft.server.level.WorldServer; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityTypes; + import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.server.MCUtil; + import org.apache.commons.lang3.tuple.MutablePair; + import org.apache.commons.lang3.tuple.Pair; + import org.bukkit.Bukkit; +@@ -41,7 +43,7 @@ import java.util.stream.Collectors; + + public class PaperCommand extends Command { + private static final String BASE_PERM = "bukkit.command.paper."; +- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version").build(); ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build(); + + public PaperCommand(String name) { + super(name); +@@ -69,6 +71,21 @@ public class PaperCommand extends Command { + if (args.length == 3) + return getListMatchingLast(sender, args, EntityTypes.getEntityNameList().stream().map(MinecraftKey::toString).sorted().toArray(String[]::new)); + break; ++ case "debug": ++ if (args.length == 2) { ++ return getListMatchingLast(sender, args, "help", "chunks"); ++ } ++ break; ++ case "chunkinfo": ++ List worldNames = new ArrayList<>(); ++ worldNames.add("*"); ++ for (org.bukkit.World world : Bukkit.getWorlds()) { ++ worldNames.add(world.getName()); ++ } ++ if (args.length == 2) { ++ return getListMatchingLast(sender, args, worldNames); ++ } ++ break; + } + return Collections.emptyList(); + } +@@ -135,6 +152,12 @@ public class PaperCommand extends Command { + case "reload": + doReload(sender); + break; ++ case "debug": ++ doDebug(sender, args); ++ break; ++ case "chunkinfo": ++ doChunkInfo(sender, args); ++ break; + case "ver": + if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) + case "version": +@@ -152,6 +175,114 @@ public class PaperCommand extends Command { + return true; + } + ++ private void doChunkInfo(CommandSender sender, String[] args) { ++ List worlds; ++ if (args.length < 2 || args[1].equals("*")) { ++ worlds = Bukkit.getWorlds(); ++ } else { ++ worlds = new ArrayList<>(args.length - 1); ++ for (int i = 1; i < args.length; ++i) { ++ org.bukkit.World world = Bukkit.getWorld(args[i]); ++ if (world == null) { ++ sender.sendMessage(ChatColor.RED + "World '" + args[i] + "' is invalid"); ++ return; ++ } ++ worlds.add(world); ++ } ++ } ++ ++ int accumulatedTotal = 0; ++ int accumulatedInactive = 0; ++ int accumulatedBorder = 0; ++ int accumulatedTicking = 0; ++ int accumulatedEntityTicking = 0; ++ ++ for (org.bukkit.World bukkitWorld : worlds) { ++ WorldServer world = ((CraftWorld)bukkitWorld).getHandle(); ++ ++ int total = 0; ++ int inactive = 0; ++ int border = 0; ++ int ticking = 0; ++ int entityTicking = 0; ++ ++ for (PlayerChunk chunk : world.getChunkProvider().playerChunkMap.updatingChunks.values()) { ++ if (chunk.getFullChunkIfCached() == null) { ++ continue; ++ } ++ ++ ++total; ++ ++ PlayerChunk.State state = PlayerChunk.getChunkState(chunk.getTicketLevel()); ++ ++ switch (state) { ++ case INACCESSIBLE: ++ ++inactive; ++ continue; ++ case BORDER: ++ ++border; ++ continue; ++ case TICKING: ++ ++ticking; ++ continue; ++ case ENTITY_TICKING: ++ ++entityTicking; ++ continue; ++ } ++ } ++ ++ accumulatedTotal += total; ++ accumulatedInactive += inactive; ++ accumulatedBorder += border; ++ accumulatedTicking += ticking; ++ accumulatedEntityTicking += entityTicking; ++ ++ sender.sendMessage(ChatColor.BLUE + "Chunks in " + ChatColor.GREEN + bukkitWorld.getName() + ChatColor.DARK_AQUA + ":"); ++ sender.sendMessage(ChatColor.BLUE + "Total: " + ChatColor.DARK_AQUA + total + ChatColor.BLUE + " Inactive: " + ChatColor.DARK_AQUA ++ + inactive + ChatColor.BLUE + " Border: " + ChatColor.DARK_AQUA + border + ChatColor.BLUE + " Ticking: " ++ + ChatColor.DARK_AQUA + ticking + ChatColor.BLUE + " Entity: " + ChatColor.DARK_AQUA + entityTicking); ++ } ++ if (worlds.size() > 1) { ++ sender.sendMessage(ChatColor.BLUE + "Chunks in " + ChatColor.GREEN + "all listed worlds" + ChatColor.DARK_AQUA + ":"); ++ sender.sendMessage(ChatColor.BLUE + "Total: " + ChatColor.DARK_AQUA + accumulatedTotal + ChatColor.BLUE + " Inactive: " + ChatColor.DARK_AQUA ++ + accumulatedInactive + ChatColor.BLUE + " Border: " + ChatColor.DARK_AQUA + accumulatedBorder + ChatColor.BLUE + " Ticking: " ++ + ChatColor.DARK_AQUA + accumulatedTicking + ChatColor.BLUE + " Entity: " + ChatColor.DARK_AQUA + accumulatedEntityTicking); ++ } ++ } ++ ++ private void doDebug(CommandSender sender, String[] args) { ++ if (args.length < 2) { ++ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command"); ++ return; ++ } ++ ++ String debugType = args[1].toLowerCase(Locale.ENGLISH); ++ switch (debugType) { ++ case "chunks": ++ if (args.length >= 3 && args[2].toLowerCase(Locale.ENGLISH).equals("help")) { ++ sender.sendMessage(ChatColor.RED + "Use /paper debug chunks to dump loaded chunk information to a file"); ++ 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(ChatColor.GREEN + "Writing chunk information dump to " + file.toString()); ++ try { ++ MCUtil.dumpChunks(file); ++ sender.sendMessage(ChatColor.GREEN + "Successfully written chunk information!"); ++ } catch (Throwable thr) { ++ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); ++ sender.sendMessage(ChatColor.RED + "Failed to dump chunk information, see console"); ++ } ++ ++ break; ++ case "help": ++ // fall through to default ++ default: ++ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command"); ++ return; ++ } ++ } ++ + /* + * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 + */ +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 35d1444c5b75d9a3a6cface5dd70aea0a08ac89d..fbd33aef21b4539d249c367609a36491530fb7ca 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -9,13 +9,27 @@ import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.chat.IChatBaseComponent; ++import net.minecraft.server.level.ChunkMapDistance; ++import net.minecraft.server.level.ChunkProviderServer; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.PlayerChunk; ++import net.minecraft.server.level.PlayerChunkMap; ++import net.minecraft.server.level.Ticket; + import net.minecraft.server.level.WorldServer; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.RayTrace; + import net.minecraft.world.level.World; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.IChunkAccess; + import org.apache.commons.lang.exception.ExceptionUtils; ++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.authlib.GameProfile; ++import com.mojang.datafixers.util.Either; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; + import org.bukkit.Location; + import org.bukkit.block.BlockFace; + import org.bukkit.craftbukkit.CraftWorld; +@@ -24,8 +38,11 @@ import org.spigotmc.AsyncCatcher; + + import javax.annotation.Nonnull; + import javax.annotation.Nullable; ++import java.io.*; ++import java.util.ArrayList; + 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; +@@ -531,4 +548,170 @@ public final class MCUtil { + + return null; + } ++ ++ public static ChunkStatus getChunkStatus(PlayerChunk chunk) { ++ List statuses = ChunkProviderServer.getPossibleChunkStatuses(); ++ for (int i = statuses.size() - 1; i >= 0; --i) { ++ ChunkStatus curr = statuses.get(i); ++ CompletableFuture> future = chunk.getStatusFutureUnchecked(curr); ++ if (future != PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE) { ++ return curr; ++ } ++ } ++ return null; // unloaded ++ } ++ ++ public static void dumpChunks(File file) throws IOException { ++ file.getParentFile().mkdirs(); ++ file.createNewFile(); ++ /* ++ * Json format: ++ * ++ * Main data format: ++ * -server-version: ++ * -data-version: ++ * -worlds: ++ * -name: ++ * -view-distance: ++ * -keep-spawn-loaded: ++ * -keep-spawn-loaded-range: ++ * -visible-chunk-count: ++ * -loaded-chunk-count: ++ * -verified-fully-loaded-chunks: ++ * -players: ++ * -chunk-data: ++ * ++ * Player format: ++ * -name: ++ * -x: ++ * -y: ++ * -z: ++ * ++ * Chunk Format: ++ * -x: ++ * -z: ++ * -ticket-level: ++ * -state: ++ * -queued-for-unload: ++ * -status: ++ * -tickets: ++ * ++ * ++ * Ticket format: ++ * -ticket-type: ++ * -ticket-level: ++ * -add-tick: ++ * -object-reason: // This depends on the type of ticket. ie POST_TELEPORT -> entity id ++ */ ++ List worlds = org.bukkit.Bukkit.getWorlds(); ++ JsonObject data = new JsonObject(); ++ ++ data.addProperty("server-version", org.bukkit.Bukkit.getVersion()); ++ data.addProperty("data-version", 0); ++ ++ JsonArray worldsData = new JsonArray(); ++ ++ for (org.bukkit.World bukkitWorld : worlds) { ++ JsonObject worldData = new JsonObject(); ++ ++ WorldServer world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); ++ PlayerChunkMap chunkMap = world.getChunkProvider().playerChunkMap; ++ Long2ObjectLinkedOpenHashMap visibleChunks = chunkMap.visibleChunks; ++ ChunkMapDistance chunkMapDistance = chunkMap.chunkDistanceManager; ++ List allChunks = new ArrayList<>(visibleChunks.values()); ++ List players = world.players; ++ ++ int fullLoadedChunks = 0; ++ ++ for (PlayerChunk chunk : allChunks) { ++ if (chunk.getFullChunkIfCached() != null) { ++ ++fullLoadedChunks; ++ } ++ } ++ ++ // sorting by coordinate makes the log easier to read ++ allChunks.sort((PlayerChunk v1, PlayerChunk v2) -> { ++ if (v1.location.x != v2.location.x) { ++ return Integer.compare(v1.location.x, v2.location.x); ++ } ++ return Integer.compare(v1.location.z, v2.location.z); ++ }); ++ ++ worldData.addProperty("name", world.getWorld().getName()); ++ worldData.addProperty("view-distance", world.spigotConfig.viewDistance); ++ worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); ++ worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange); ++ worldData.addProperty("visible-chunk-count", visibleChunks.size()); ++ worldData.addProperty("loaded-chunk-count", chunkMap.loadedChunks.size()); ++ worldData.addProperty("verified-fully-loaded-chunks", fullLoadedChunks); ++ ++ JsonArray playersData = new JsonArray(); ++ ++ for (EntityPlayer player : players) { ++ JsonObject playerData = new JsonObject(); ++ ++ playerData.addProperty("name", player.getName()); ++ playerData.addProperty("x", player.locX()); ++ playerData.addProperty("y", player.locY()); ++ playerData.addProperty("z", player.locZ()); ++ ++ playersData.add(playerData); ++ ++ } ++ ++ worldData.add("players", playersData); ++ ++ JsonArray chunksData = new JsonArray(); ++ ++ for (PlayerChunk playerChunk : allChunks) { ++ JsonObject chunkData = new JsonObject(); ++ ++ Set> tickets = chunkMapDistance.tickets.get(playerChunk.location.pair()); ++ ChunkStatus status = getChunkStatus(playerChunk); ++ ++ chunkData.addProperty("x", playerChunk.location.x); ++ chunkData.addProperty("z", playerChunk.location.z); ++ chunkData.addProperty("ticket-level", playerChunk.getTicketLevel()); ++ chunkData.addProperty("state", PlayerChunk.getChunkState(playerChunk.getTicketLevel()).toString()); ++ chunkData.addProperty("queued-for-unload", chunkMap.unloadQueue.contains(playerChunk.location.pair())); ++ chunkData.addProperty("status", status == null ? "unloaded" : status.toString()); ++ ++ JsonArray ticketsData = new JsonArray(); ++ ++ if (tickets != null) { ++ for (Ticket ticket : tickets) { ++ JsonObject ticketData = new JsonObject(); ++ ++ ticketData.addProperty("ticket-type", ticket.getTicketType().toString()); ++ ticketData.addProperty("ticket-level", ticket.getTicketLevel()); ++ ticketData.addProperty("object-reason", String.valueOf(ticket.getObjectReason())); ++ ticketData.addProperty("add-tick", ticket.getCreationTick()); ++ ++ ticketsData.add(ticketData); ++ } ++ } ++ ++ chunkData.add("tickets", ticketsData); ++ chunksData.add(chunkData); ++ } ++ ++ ++ worldData.add("chunk-data", chunksData); ++ 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, "UTF-8")) { ++ out.print(fileData); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index bd8f58b398ea5afe3d4785865a40e22eaf266926..12eb9ec2064b006f268308f63167d43c6daa92dc 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -46,7 +46,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper + + public class ChunkProviderServer extends IChunkProvider { + +- private static final List b = ChunkStatus.a(); ++ private static final List b = ChunkStatus.a(); public static final List getPossibleChunkStatuses() { return ChunkProviderServer.b; } // Paper - OBFHELPER + private final ChunkMapDistance chunkMapDistance; + public final ChunkGenerator chunkGenerator; + private final WorldServer world; +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 2c3d9a5d118cc4f3b5e78daf943911bb7386488a..9891cf98f8c740f84f9135ee8176e67abb648b3a 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -54,7 +54,7 @@ public class PlayerChunk { + public int oldTicketLevel; + private int ticketLevel; + private int n; +- private final ChunkCoordIntPair location; ++ final ChunkCoordIntPair location; // Paper - private -> package + private boolean p; + private final ShortSet[] dirtyBlocks; + private int r; +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index aeed11cfee42fbde2c2e5731f46ac24de6469e0e..a0fcd20d4a7e951437756edb60a44c627612e04c 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -106,7 +106,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + public final Long2ObjectLinkedOpenHashMap updatingChunks = new Long2ObjectLinkedOpenHashMap(); + public volatile Long2ObjectLinkedOpenHashMap visibleChunks; + private final Long2ObjectLinkedOpenHashMap pendingUnload; +- private final LongSet loadedChunks; ++ public final LongSet loadedChunks; // Paper - private -> public + public final WorldServer world; + private final LightEngineThreaded lightEngine; + private final IAsyncTaskHandler executor; +diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java +index 51ef4adf66c1e21093e63ab46fa47e66c2425fdb..e06fe77f6ea05a93e95fce223bcfd0d16394f96f 100644 +--- a/src/main/java/net/minecraft/server/level/Ticket.java ++++ b/src/main/java/net/minecraft/server/level/Ticket.java +@@ -6,8 +6,8 @@ public final class Ticket implements Comparable> { + + private final TicketType a; + private final int b; +- public final T identifier; +- private long d; ++ public final T identifier; public final T getObjectReason() { return this.identifier; } // Paper - OBFHELPER ++ private long d; public final long getCreationTick() { return this.d; } // Paper - OBFHELPER + + protected Ticket(TicketType tickettype, int i, T t0) { + this.a = tickettype; +@@ -51,6 +51,7 @@ public final class Ticket implements Comparable> { + return this.a; + } + ++ public final int getTicketLevel() { return this.b(); } // Paper - OBFHELPER + public int b() { + return this.b; + } diff --git a/patches/server-unmapped/0001/0359-Catch-exceptions-from-dispenser-entity-spawns.patch b/patches/server-unmapped/0001/0359-Catch-exceptions-from-dispenser-entity-spawns.patch new file mode 100644 index 0000000000..f724360c49 --- /dev/null +++ b/patches/server-unmapped/0001/0359-Catch-exceptions-from-dispenser-entity-spawns.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 10 Jun 2019 09:36:40 +0100 +Subject: [PATCH] Catch exceptions from dispenser entity spawns + + +diff --git a/src/main/java/net/minecraft/core/dispenser/IDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/IDispenseBehavior.java +index ffce5baaca6fd5b5e73ed898d12a4fee02c24515..158075319bd49ac78ea994639cdad21aeacdf86f 100644 +--- a/src/main/java/net/minecraft/core/dispenser/IDispenseBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/IDispenseBehavior.java +@@ -8,6 +8,7 @@ import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; + import net.minecraft.core.IPosition; + import net.minecraft.core.ISourceBlock; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.WorldServer; + import net.minecraft.sounds.SoundCategory; +@@ -236,7 +237,14 @@ public interface IDispenseBehavior { + } + } + ++ try { // Paper + entitytypes.spawnCreature(isourceblock.getWorld(), itemstack, (EntityHuman) null, isourceblock.getBlockPosition().shift(enumdirection), EnumMobSpawn.DISPENSER, enumdirection != EnumDirection.UP, false); ++ // Paper start ++ } catch (Exception ex){ ++ MinecraftServer.LOGGER.warn("An exception occurred dispensing entity at {}[{}]", worldserver.getWorld().getName(), isourceblock.getBlockPosition(), ex); ++ } ++ // Paper end ++ + // itemstack.subtract(1); // Handled during event processing + // CraftBukkit end + return itemstack; diff --git a/patches/server-unmapped/0001/0360-Fix-World-isChunkGenerated-calls.patch b/patches/server-unmapped/0001/0360-Fix-World-isChunkGenerated-calls.patch new file mode 100644 index 0000000000..b9e77c9637 --- /dev/null +++ b/patches/server-unmapped/0001/0360-Fix-World-isChunkGenerated-calls.patch @@ -0,0 +1,390 @@ +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/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index 12eb9ec2064b006f268308f63167d43c6daa92dc..80c3b022368917c173951fcf6fd959a6b0f05c41 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -52,7 +52,7 @@ public class ChunkProviderServer extends IChunkProvider { + private final WorldServer world; + public final Thread serverThread; // Paper - private -> public + private final LightEngineThreaded lightEngine; +- private final ChunkProviderServer.a serverThreadQueue; ++ public final ChunkProviderServer.a serverThreadQueue; // Paper private -> public + public final PlayerChunkMap playerChunkMap; + private final WorldPersistentData worldPersistentData; + private long lastTickTime; +@@ -317,6 +317,21 @@ public class ChunkProviderServer extends IChunkProvider { + + return ret; + } ++ ++ @Nullable ++ public IChunkAccess getChunkAtImmediately(int x, int z) { ++ long k = ChunkCoordIntPair.pair(x, z); ++ ++ // Note: Bypass cache to make this MT-Safe ++ ++ PlayerChunk playerChunk = this.getChunk(k); ++ if (playerChunk == null) { ++ return null; ++ } ++ ++ return playerChunk.getAvailableChunkNow(); ++ ++ } + // Paper end + + @Nullable +@@ -769,7 +784,7 @@ public class ChunkProviderServer extends IChunkProvider { + return this.p; + } + +- final class a extends IAsyncTaskHandler { ++ public final class a extends IAsyncTaskHandler { // Paper - package -> public + + private a(World world) { + super("Chunk source main thread executor for " + world.getDimensionKey().a()); +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 9891cf98f8c740f84f9135ee8176e67abb648b3a..6bced8533df49d7bfdb32dfa0caad9d788ffc2c8 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -142,6 +142,19 @@ public class PlayerChunk { + Either either = (Either) statusFuture.getNow(null); + return either == null ? null : (Chunk) either.left().orElse(null); + } ++ ++ public IChunkAccess getAvailableChunkNow() { ++ // TODO can we just getStatusFuture(EMPTY)? ++ for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getPreviousStatus(); curr != next; curr = next, next = next.getPreviousStatus()) { ++ CompletableFuture> future = this.getStatusFutureUnchecked(curr); ++ Either either = future.getNow(null); ++ if (either == null || !either.left().isPresent()) { ++ continue; ++ } ++ return either.left().get(); ++ } ++ return null; ++ } + // Paper end + + public CompletableFuture> getStatusFutureUnchecked(ChunkStatus chunkstatus) { +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index a0fcd20d4a7e951437756edb60a44c627612e04c..ccfde274edfe1b611ccf8c583c92b16d52e4518d 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -985,12 +985,61 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + @Nullable +- private NBTTagCompound readChunkData(ChunkCoordIntPair chunkcoordintpair) throws IOException { ++ public NBTTagCompound readChunkData(ChunkCoordIntPair chunkcoordintpair) throws IOException { // Paper - private -> public + NBTTagCompound nbttagcompound = this.read(chunkcoordintpair); ++ // Paper start - Cache chunk status on disk ++ if (nbttagcompound == null) { ++ return null; ++ } ++ ++ nbttagcompound = this.getChunkData(this.world.getTypeKey(), this.l, nbttagcompound, chunkcoordintpair, world); // CraftBukkit ++ if (nbttagcompound == null) { ++ return null; ++ } ++ ++ this.updateChunkStatusOnDisk(chunkcoordintpair, nbttagcompound); ++ ++ return nbttagcompound; ++ // Paper end ++ } ++ ++ // Paper start - chunk status cache "api" ++ public ChunkStatus getChunkStatusOnDiskIfCached(ChunkCoordIntPair chunkPos) { ++ RegionFile regionFile = this.getIOWorker().getRegionFileCache().getRegionFileIfLoaded(chunkPos); ++ ++ return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ } ++ ++ public ChunkStatus getChunkStatusOnDisk(ChunkCoordIntPair chunkPos) throws IOException { ++ RegionFile regionFile = this.getIOWorker().getRegionFileCache().getFile(chunkPos, true); ++ ++ if (regionFile == null || !regionFile.chunkExists(chunkPos)) { ++ return null; ++ } ++ ++ ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ ++ if (status != null) { ++ return status; ++ } ++ ++ this.readChunkData(chunkPos); + +- return nbttagcompound == null ? null : this.getChunkData(this.world.getTypeKey(), this.l, nbttagcompound, chunkcoordintpair, world); // CraftBukkit ++ return regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); + } + ++ public void updateChunkStatusOnDisk(ChunkCoordIntPair chunkPos, @Nullable NBTTagCompound compound) throws IOException { ++ RegionFile regionFile = this.getIOWorker().getRegionFileCache().getFile(chunkPos, false); ++ ++ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkRegionLoader.getStatus(compound)); ++ } ++ ++ public IChunkAccess getUnloadingChunk(int chunkX, int chunkZ) { ++ PlayerChunk chunkHolder = this.pendingUnload.get(ChunkCoordIntPair.pair(chunkX, chunkZ)); ++ return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow(); ++ } ++ // Paper end ++ + boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair) { + // Spigot start + return isOutsideOfRange(chunkcoordintpair, false); +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 5e4c162654349f884becc10e8fbae4ded6889deb..711308cf84a816f09d116a7414f9cbee803c8713 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +@@ -193,6 +193,7 @@ public class ChunkStatus { + return this.s; + } + ++ public ChunkStatus getPreviousStatus() { return this.e(); } // Paper - OBFHELPER + public ChunkStatus e() { + return this.u; + } +@@ -213,6 +214,17 @@ public class ChunkStatus { + return this.y; + } + ++ // Paper start ++ public static ChunkStatus getStatus(String name) { ++ try { ++ // We need this otherwise we return EMPTY for invalid names ++ MinecraftKey key = new MinecraftKey(name); ++ return IRegistry.CHUNK_STATUS.getOptional(key).orElse(null); ++ } catch (Exception ex) { ++ return null; // invalid name ++ } ++ } ++ // Paper end + public static ChunkStatus a(String s) { + return (ChunkStatus) IRegistry.CHUNK_STATUS.get(MinecraftKey.a(s)); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +index 69bc9dc18bab157851d8080a672504598e8572a8..98bc26c7ae01884eb53766e72fc7cbabbf065e6e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +@@ -462,6 +462,17 @@ public class ChunkRegionLoader { + } + // Paper end + ++ // Paper start ++ public static ChunkStatus getStatus(NBTTagCompound compound) { ++ if (compound == null) { ++ return null; ++ } ++ ++ // Note: Copied from below ++ return ChunkStatus.getStatus(compound.getCompound("Level").getString("Status")); ++ } ++ // Paper end ++ + public static ChunkStatus.Type a(@Nullable NBTTagCompound nbttagcompound) { + if (nbttagcompound != null) { + ChunkStatus chunkstatus = ChunkStatus.a(nbttagcompound.getCompound("Level").getString("Status")); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java +index 247d14a3ca56734bbbf4dc0ec247d60a1f241e7a..d785f44cd503d4d91589f3fc4bc8dc805dff3d41 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java +@@ -25,7 +25,7 @@ import net.minecraft.world.level.dimension.DimensionManager; + + public class IChunkLoader implements AutoCloseable { + +- private final IOWorker a; ++ private final IOWorker a; public IOWorker getIOWorker() { return a; } // Paper - OBFHELPER + protected final DataFixer b; + @Nullable + private PersistentStructureLegacy c; +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 00cef1c0bc19976a000389e57a1af5d93690c0e7..d50b9c9d030016f951e2ed7fb519250b7408c833 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 +@@ -27,6 +27,7 @@ import net.minecraft.SystemUtils; + import net.minecraft.nbt.NBTCompressedStreamTools; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.chunk.ChunkStatus; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + +@@ -44,6 +45,30 @@ public class RegionFile implements AutoCloseable { + protected final RegionFileBitSet freeSectors; + public final File file; // Paper + ++ // Paper start - Cache chunk status ++ private final ChunkStatus[] statuses = new ChunkStatus[32 * 32]; ++ ++ private boolean closed; ++ ++ // invoked on write/read ++ public void setStatus(int x, int z, 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 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 ++ + public RegionFile(File file, File file1, boolean flag) throws IOException { + this(file.toPath(), file1.toPath(), RegionFileCompression.b, flag); + } +@@ -380,11 +405,13 @@ public class RegionFile implements AutoCloseable { + return this.getOffset(chunkcoordintpair) != 0; + } + ++ private static int getChunkLocation(int x, int z) { return (x & 31) + (z & 31) * 32; } // Paper - OBFHELPER - sort of, mirror of logic below + private static int g(ChunkCoordIntPair chunkcoordintpair) { + return chunkcoordintpair.j() + chunkcoordintpair.k() * 32; + } + + public void close() throws IOException { ++ this.closed = true; // Paper + try { + this.d(); + } finally { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java +index ab9f4d40fd1126a3d7ba5b16fdc6ab09de4a7fdb..55e7e983d2c760a8052d7b3ddbdc8447f619a60f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java +@@ -28,7 +28,14 @@ public final class RegionFileCache implements AutoCloseable { + this.c = flag; + } + +- private RegionFile getFile(ChunkCoordIntPair chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit ++ ++ // Paper start ++ public RegionFile getRegionFileIfLoaded(ChunkCoordIntPair chunkcoordintpair) { ++ return this.cache.getAndMoveToFirst(ChunkCoordIntPair.pair(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); ++ } ++ ++ // Paper end ++ public RegionFile getFile(ChunkCoordIntPair chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - private > public + long i = ChunkCoordIntPair.pair(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); + RegionFile regionfile = (RegionFile) this.cache.getAndMoveToFirst(i); + +@@ -175,6 +182,7 @@ public final class RegionFileCache implements AutoCloseable { + + try { + NBTCompressedStreamTools.a(nbttagcompound, (DataOutput) dataoutputstream); ++ regionfile.setStatus(chunkcoordintpair.x, chunkcoordintpair.z, ChunkRegionLoader.getStatus(nbttagcompound)); // Paper - cache status on disk + regionfile.setOversized(chunkcoordintpair.x, chunkcoordintpair.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 + } catch (Throwable throwable1) { + throwable = throwable1; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index dd9fe7d04666ff6173f9b39e6450bb1165cc9e8a..daa5c52a690ce33d2b2cd32c4fbaa147f25dd144 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -19,6 +19,7 @@ import java.util.Objects; + import java.util.Random; + import java.util.Set; + import java.util.UUID; ++import java.util.concurrent.CompletableFuture; + import java.util.function.Predicate; + import java.util.stream.Collectors; + import net.minecraft.core.BlockPosition; +@@ -419,8 +420,22 @@ public class CraftWorld implements World { + + @Override + public boolean isChunkGenerated(int x, int z) { ++ // Paper start - Fix this method ++ if (!Bukkit.isPrimaryThread()) { ++ return CompletableFuture.supplyAsync(() -> { ++ return CraftWorld.this.isChunkGenerated(x, z); ++ }, world.getChunkProvider().serverThreadQueue).join(); ++ } ++ IChunkAccess chunk = world.getChunkProvider().getChunkAtImmediately(x, z); ++ if (chunk == null) { ++ chunk = world.getChunkProvider().playerChunkMap.getUnloadingChunk(x, z); ++ } ++ if (chunk != null) { ++ return chunk instanceof ProtoChunkExtension || chunk instanceof net.minecraft.world.level.chunk.Chunk; ++ } + try { +- return world.getChunkProvider().getChunkAtIfCachedImmediately(x, z) != null || world.getChunkProvider().playerChunkMap.read(new ChunkCoordIntPair(x, z)) != null; // Paper (TODO check if the first part can be removed) ++ return world.getChunkProvider().playerChunkMap.getChunkStatusOnDisk(new ChunkCoordIntPair(x, z)) == ChunkStatus.FULL; ++ // Paper end + } catch (IOException ex) { + throw new RuntimeException(ex); + } +@@ -531,20 +546,48 @@ public class CraftWorld implements World { + @Override + public boolean loadChunk(int x, int z, boolean generate) { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot +- IChunkAccess chunk = world.getChunkProvider().getChunkAt(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper ++ // Paper start - Optimize this method ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(x, z); + +- // If generate = false, but the chunk already exists, we will get this back. +- if (chunk instanceof ProtoChunkExtension) { +- // We then cycle through again to get the full chunk immediately, rather than after the ticket addition +- chunk = world.getChunkProvider().getChunkAt(x, z, ChunkStatus.FULL, true); +- } ++ if (!generate) { ++ IChunkAccess immediate = world.getChunkProvider().getChunkAtImmediately(x, z); ++ if (immediate == null) { ++ immediate = world.getChunkProvider().playerChunkMap.getUnloadingChunk(x, z); ++ } ++ if (immediate != null) { ++ if (!(immediate instanceof ProtoChunkExtension) && !(immediate instanceof net.minecraft.world.level.chunk.Chunk)) { ++ return false; // not full status ++ } ++ world.getChunkProvider().addTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); ++ world.getChunkAt(x, z); // make sure we're at ticket level 32 or lower ++ return true; ++ } + +- if (chunk instanceof net.minecraft.world.level.chunk.Chunk) { +- world.getChunkProvider().addTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 1, Unit.INSTANCE); +- return true; ++ net.minecraft.world.level.chunk.storage.RegionFile file; ++ try { ++ file = world.getChunkProvider().playerChunkMap.getIOWorker().getRegionFileCache().getFile(chunkPos, false); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ ChunkStatus status = file.getStatusIfCached(x, z); ++ if (!file.chunkExists(chunkPos) || (status != null && status != ChunkStatus.FULL)) { ++ return false; ++ } ++ ++ IChunkAccess chunk = world.getChunkProvider().getChunkAt(x, z, ChunkStatus.EMPTY, true); ++ if (!(chunk instanceof ProtoChunkExtension) && !(chunk instanceof net.minecraft.world.level.chunk.Chunk)) { ++ return false; ++ } ++ ++ // fall through to load ++ // we do this so we do not re-read the chunk data on disk + } + +- return false; ++ world.getChunkProvider().addTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); ++ world.getChunkProvider().getChunkAt(x, z, ChunkStatus.FULL, true); ++ return true; ++ // Paper end + } + + @Override diff --git a/patches/server-unmapped/0001/0361-Show-blockstate-location-if-we-failed-to-read-it.patch b/patches/server-unmapped/0001/0361-Show-blockstate-location-if-we-failed-to-read-it.patch new file mode 100644 index 0000000000..8c64eb071a --- /dev/null +++ b/patches/server-unmapped/0001/0361-Show-blockstate-location-if-we-failed-to-read-it.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 15 Jun 2019 10:28:25 -0700 +Subject: [PATCH] Show blockstate location if we failed to read it + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index 2f0b48869077c27d0cacea81a99c9e34ff59c684..a4bd0d352c2babdbb31cdf49d63e2db3af4de146 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -19,6 +19,8 @@ public class CraftBlockEntityState extends CraftBlockState + public CraftBlockEntityState(Block block, Class tileEntityClass) { + super(block); + ++ try {// Paper - show location on failure ++ + this.tileEntityClass = tileEntityClass; + + // get tile entity from block: +@@ -38,6 +40,14 @@ public class CraftBlockEntityState extends CraftBlockState + this.load(this.snapshot); + } + // Paper end ++ // Paper start - show location on failure ++ } catch (Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ throw new RuntimeException("Failed to read BlockState at: world: " + block.getWorld().getName() + " location: (" + block.getX() + ", " + block.getY() + ", " + block.getZ() + ")", thr); ++ } ++ // Paper end + } + + public final boolean snapshotDisabled; // Paper diff --git a/patches/server-unmapped/0001/0362-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch b/patches/server-unmapped/0001/0362-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch new file mode 100644 index 0000000000..1dcdb2ef23 --- /dev/null +++ b/patches/server-unmapped/0001/0362-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch @@ -0,0 +1,101 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 May 2020 20:29:02 -0400 +Subject: [PATCH] Synchronize DataPaletteBlock instead of ReentrantLock + +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 wrote locks for low +contention situations. + +And this is extremely a low contention situation. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +index 8928157b01bb4f0dfe043732777b33708c23cda7..cc0c5995dc3840ce66ea849849f7c37555d3b5e6 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java ++++ b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +@@ -32,7 +32,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { + private int i; private int getBitsPerObject() { return this.i; } // Paper - OBFHELPER + private final ReentrantLock j = new ReentrantLock(); + +- public void a() { ++ public void a() { /* // Paper start - disable this - use proper synchronization + if (this.j.isLocked() && !this.j.isHeldByCurrentThread()) { + String s = (String) Thread.getAllStackTraces().keySet().stream().filter(Objects::nonNull).map((thread) -> { + return thread.getName() + ": \n\tat " + (String) Arrays.stream(thread.getStackTrace()).map(Object::toString).collect(Collectors.joining("\n\tat ")); +@@ -44,11 +44,11 @@ public class DataPaletteBlock implements DataPaletteExpandable { + throw new ReportedException(crashreport); + } else { + this.j.lock(); +- } ++ } */ // Paper end + } + + public void b() { +- this.j.unlock(); ++ //this.j.unlock(); // Paper - disable this + } + + public DataPaletteBlock(DataPalette datapalette, RegistryBlockID registryblockid, Function function, Function function1, T t0) { +@@ -84,7 +84,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { + } + + @Override +- public int onResize(int i, T t0) { ++ public synchronized int onResize(int i, T t0) { // Paper - synchronize + this.a(); + DataBits databits = this.a; + DataPalette datapalette = this.h; +@@ -107,18 +107,18 @@ public class DataPaletteBlock implements DataPaletteExpandable { + } + + public T setBlock(int i, int j, int k, T t0) { +- this.a(); +- T t1 = this.a(b(i, j, k), t0); ++ //this.a(); // Paper - remove to reduce ops - synchronize handled below ++ return this.a(b(i, j, k), t0); // Paper + +- this.b(); +- return t1; ++ //this.b(); // Paper ++ //return t1; // PAper + } + + public T b(int i, int j, int k, T t0) { + return this.a(b(i, j, k), t0); + } + +- protected T a(int i, T t0) { ++ protected synchronized T a(int i, T t0) { // Paper - synchronize - writes + int j = this.h.a(t0); + int k = this.a.a(i, j); + T t1 = this.h.a(k); +@@ -143,7 +143,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { + } + + public void writeDataPaletteBlock(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // Paper - OBFHELPER +- public void b(PacketDataSerializer packetdataserializer) { ++ public synchronized void b(PacketDataSerializer packetdataserializer) { // Paper - synchronize + this.a(); + packetdataserializer.writeByte(this.i); + this.h.b(packetdataserializer); +@@ -151,7 +151,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { + this.b(); + } + +- public void a(NBTTagList nbttaglist, long[] along) { ++ public synchronized void a(NBTTagList nbttaglist, long[] along) { // Paper - synchronize + this.a(); + int i = Math.max(4, MathHelper.e(nbttaglist.size())); + +@@ -184,7 +184,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { + this.b(); + } + +- public void a(NBTTagCompound nbttagcompound, String s, String s1) { ++ public synchronized void a(NBTTagCompound nbttagcompound, String s, String s1) { // Paper - synchronize + this.a(); + DataPaletteHash datapalettehash = new DataPaletteHash<>(this.d, this.i, this.c, this.e, this.f); + T t0 = this.g; diff --git a/patches/server-unmapped/0001/0363-incremental-chunk-saving.patch b/patches/server-unmapped/0001/0363-incremental-chunk-saving.patch new file mode 100644 index 0000000000..7befadadd5 --- /dev/null +++ b/patches/server-unmapped/0001/0363-incremental-chunk-saving.patch @@ -0,0 +1,323 @@ +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 saving + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index ffe9b1a63d78925e1d77b9e730aef42fed6d58fa..1278d09f70c1e97607ef20d87a178dc252c7f723 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -446,4 +446,19 @@ public class PaperWorldConfig { + keepLoadedRange = (short) (getInt("keep-spawn-loaded-range", Math.min(spigotConfig.viewDistance, 10)) * 16); + log( "Keep Spawn Loaded Range: " + (keepLoadedRange/16)); + } ++ ++ public int autoSavePeriod = -1; ++ private void autoSavePeriod() { ++ autoSavePeriod = getInt("auto-save-interval", -1); ++ if (autoSavePeriod > 0) { ++ log("Auto Save Interval: " +autoSavePeriod + " (" + (autoSavePeriod / 20) + "s)"); ++ } else if (autoSavePeriod < 0) { ++ autoSavePeriod = net.minecraft.server.MinecraftServer.getServer().autosavePeriod; ++ } ++ } ++ ++ public int maxAutoSaveChunksPerTick = 24; ++ private void maxAutoSaveChunksPerTick() { ++ maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 0aa723d9940f4b33cd587aef6e23e238c2c22359..9e6d70bdf4d1cf524e45b55ee51d87a30394ac84 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -262,6 +262,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; ++ public boolean serverAutoSave = false; // Paper + public CommandDispatcher vanillaCommandDispatcher; + private boolean forceTicks; + // CraftBukkit end +@@ -1257,14 +1258,24 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit +- MinecraftServer.LOGGER.debug("Autosave started"); ++ //if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit // Paper - move down ++ //MinecraftServer.LOGGER.debug("Autosave started"); // Paper ++ serverAutoSave = (autosavePeriod > 0 && this.ticks % autosavePeriod == 0); // Paper + this.methodProfiler.enter("save"); ++ if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // Paper + this.playerList.savePlayers(); +- this.saveChunks(true, false, false); ++ }// Paper ++ // Paper start ++ for (WorldServer world : getWorlds()) { ++ if (world.paperConfig.autoSavePeriod > 0) { ++ world.saveIncrementally(serverAutoSave); ++ } ++ } ++ // Paper end ++ + this.methodProfiler.exit(); +- MinecraftServer.LOGGER.debug("Autosave finished"); +- } ++ //MinecraftServer.LOGGER.debug("Autosave finished"); // Paper ++ //} // Paper + + this.methodProfiler.enter("snooper"); + if (((DedicatedServer) this).getDedicatedServerProperties().snooperEnabled && !this.snooper.d() && this.ticks > 100) { // Spigot +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index 80c3b022368917c173951fcf6fd959a6b0f05c41..6d5ad6482666574bfb6b00655fab332f52a52927 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -558,6 +558,15 @@ public class ChunkProviderServer extends IChunkProvider { + } // Paper - Timings + } + ++ // Paper start - duplicate save, but call incremental ++ public void saveIncrementally() { ++ this.tickDistanceManager(); ++ try (co.aikar.timings.Timing timed = world.timings.chunkSaveData.startTiming()) { // Paper - Timings ++ this.playerChunkMap.saveIncrementally(); ++ } // Paper - Timings ++ } ++ // Paper end ++ + @Override + public void close() throws IOException { + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 6bced8533df49d7bfdb32dfa0caad9d788ffc2c8..75d4a8fc394449ccc006fe67a8842edcd9f36854 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -67,6 +67,9 @@ public class PlayerChunk { + + private final PlayerChunkMap chunkMap; // Paper + ++ long lastAutoSaveTime; // Paper - incremental autosave ++ long inactiveTimeStart; // Paper - incremental autosave ++ + public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { + this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); + this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; +@@ -422,7 +425,19 @@ public class PlayerChunk { + boolean flag2 = playerchunk_state.isAtLeast(PlayerChunk.State.BORDER); + boolean flag3 = playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER); + ++ boolean prevHasBeenLoaded = this.hasBeenLoaded; // Paper + this.hasBeenLoaded |= flag3; ++ // Paper start - incremental autosave ++ if (this.hasBeenLoaded & !prevHasBeenLoaded) { ++ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime; ++ if (timeSinceAutoSave < 0) { ++ // safest bet is to assume autosave is needed here ++ timeSinceAutoSave = this.chunkMap.world.paperConfig.autoSavePeriod; ++ } ++ this.lastAutoSaveTime = this.chunkMap.world.getTime() - timeSinceAutoSave; ++ this.chunkMap.autoSaveQueue.add(this); ++ } ++ // Paper end + if (!flag2 && flag3) { + // Paper start - cache ticking ready status + int expectCreateCount = ++this.fullChunkCreateCount; +@@ -542,8 +557,32 @@ public class PlayerChunk { + } + + public void m() { ++ boolean prev = this.hasBeenLoaded; // Paper ++ this.hasBeenLoaded = getChunkState(this.ticketLevel).isAtLeast(PlayerChunk.State.BORDER); ++ // Paper start - incremental autosave ++ if (prev != this.hasBeenLoaded) { ++ if (this.hasBeenLoaded) { ++ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime; ++ if (timeSinceAutoSave < 0) { ++ // safest bet is to assume autosave is needed here ++ timeSinceAutoSave = this.chunkMap.world.paperConfig.autoSavePeriod; ++ } ++ this.lastAutoSaveTime = this.chunkMap.world.getTime() - timeSinceAutoSave; ++ this.chunkMap.autoSaveQueue.add(this); ++ } else { ++ this.inactiveTimeStart = this.chunkMap.world.getTime(); ++ this.chunkMap.autoSaveQueue.remove(this); ++ } ++ } ++ // Paper end ++ } ++ ++ // Paper start - incremental autosave ++ public boolean setHasBeenLoaded() { + this.hasBeenLoaded = getChunkState(this.ticketLevel).isAtLeast(PlayerChunk.State.BORDER); ++ return this.hasBeenLoaded; + } ++ // Paper end + + public void a(ProtoChunkExtension protochunkextension) { + for (int i = 0; i < this.statusFutures.length(); ++i) { +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index ccfde274edfe1b611ccf8c583c92b16d52e4518d..1f32ab230d650bb5f652efbacdd5e4b90dc4de89 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -93,6 +93,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStruct + import net.minecraft.world.level.storage.Convertable; + import net.minecraft.world.level.storage.WorldPersistentData; + import net.minecraft.world.phys.Vec3D; ++import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper + import org.apache.commons.lang3.mutable.MutableBoolean; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -380,6 +381,64 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + } + ++ // Paper start - incremental autosave ++ final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((playerchunk1, playerchunk2) -> { ++ int timeCompare = Long.compare(playerchunk1.lastAutoSaveTime, playerchunk2.lastAutoSaveTime); ++ if (timeCompare != 0) { ++ return timeCompare; ++ } ++ ++ return Long.compare(MCUtil.getCoordinateKey(playerchunk1.location), MCUtil.getCoordinateKey(playerchunk2.location)); ++ }); ++ ++ protected void saveIncrementally() { ++ int savedThisTick = 0; ++ // optimized since we search far less chunks to hit ones that need to be saved ++ List reschedule = new java.util.ArrayList<>(this.world.paperConfig.maxAutoSaveChunksPerTick); ++ long currentTick = this.world.getTime(); ++ long maxSaveTime = currentTick - this.world.paperConfig.autoSavePeriod; ++ ++ for (Iterator iterator = this.autoSaveQueue.iterator(); iterator.hasNext();) { ++ PlayerChunk playerchunk = iterator.next(); ++ if (playerchunk.lastAutoSaveTime > maxSaveTime) { ++ break; ++ } ++ ++ iterator.remove(); ++ ++ IChunkAccess ichunkaccess = playerchunk.getChunkSave().getNow(null); ++ if (ichunkaccess instanceof Chunk) { ++ boolean shouldSave = ((Chunk)ichunkaccess).lastSaved <= maxSaveTime; ++ ++ if (shouldSave && this.saveChunk(ichunkaccess)) { ++ ++savedThisTick; ++ ++ if (!playerchunk.setHasBeenLoaded()) { ++ // do not fall through to reschedule logic ++ playerchunk.inactiveTimeStart = currentTick; ++ if (savedThisTick >= this.world.paperConfig.maxAutoSaveChunksPerTick) { ++ break; ++ } ++ continue; ++ } ++ } ++ } ++ ++ reschedule.add(playerchunk); ++ ++ if (savedThisTick >= this.world.paperConfig.maxAutoSaveChunksPerTick) { ++ break; ++ } ++ } ++ ++ for (int i = 0, len = reschedule.size(); i < len; ++i) { ++ PlayerChunk playerchunk = reschedule.get(i); ++ playerchunk.lastAutoSaveTime = this.world.getTime(); ++ this.autoSaveQueue.add(playerchunk); ++ } ++ } ++ // Paper end ++ + protected void save(boolean flag) { + if (flag) { + List list = (List) this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).peek(PlayerChunk::m).collect(Collectors.toList()); +@@ -490,6 +549,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + this.world.unloadChunk(chunk); + } ++ this.autoSaveQueue.remove(playerchunk); // Paper + + this.lightEngine.a(ichunkaccess.getPos()); + this.lightEngine.queueUpdate(); +@@ -682,6 +742,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + playerchunk.a(new ProtoChunkExtension(chunk)); + } + ++ chunk.setLastSaved(this.world.getTime() - 1); // Paper - avoid autosaving newly generated/loaded chunks ++ + chunk.a(() -> { + return PlayerChunk.getChunkState(playerchunk.getTicketLevel()); + }); +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index a9c0d3fc4aa07d9d580a31106169796b7bde4e63..735da5729c16940e3d8877f32a40342b9d1e989d 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -888,11 +888,43 @@ public class WorldServer extends World implements GeneratorAccessSeed { + return !this.server.a(this, blockposition, entityhuman) && this.getWorldBorder().a(blockposition); + } + ++ // Paper start - derived from below ++ public void saveIncrementally(boolean doFull) { ++ ChunkProviderServer chunkproviderserver = this.getChunkProvider(); ++ ++ if (doFull) { ++ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); ++ } ++ ++ try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { ++ if (doFull) { ++ this.saveData(); ++ } ++ ++ timings.worldSaveChunks.startTiming(); // Paper ++ if (!this.isSavingDisabled()) chunkproviderserver.saveIncrementally(); ++ timings.worldSaveChunks.stopTiming(); // Paper ++ ++ ++ // Copied from save() ++ // CraftBukkit start - moved from MinecraftServer.saveChunks ++ if (doFull) { // Paper ++ WorldServer worldserver1 = this; ++ ++ worldDataServer.a(worldserver1.getWorldBorder().t()); ++ worldDataServer.setCustomBossEvents(this.server.getBossBattleCustomData().save()); ++ convertable.a(this.server.customRegistry, this.worldDataServer, this.server.getPlayerList().save()); ++ } ++ // CraftBukkit end ++ } ++ } ++ // Paper end ++ + public void save(@Nullable IProgressUpdate iprogressupdate, boolean flag, boolean flag1) { + ChunkProviderServer chunkproviderserver = this.getChunkProvider(); + + if (!flag1) { +- org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit ++ if (flag) org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit // Paper + try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper + if (iprogressupdate != null) { + iprogressupdate.a(new ChatMessage("menu.savingLevel")); +@@ -918,6 +950,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + // CraftBukkit end + } + ++ private void saveData() { this.aj(); } // Paper - OBFHELPER + private void aj() { + if (this.dragonBattle != null) { + this.worldDataServer.a(this.dragonBattle.a()); // CraftBukkit +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index 3f926ed8e2b2c9dbf1e2493870af7eff3b6db019..2690c44eaae193a259fe195c95e59d07d5e1cc5a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -81,7 +81,7 @@ public class Chunk implements IChunkAccess { + private TickList o; + private TickList p; + private boolean q; +- private long lastSaved; ++ public long lastSaved; // Paper + private volatile boolean s; + private long inhabitedTime; + @Nullable diff --git a/patches/server-unmapped/0001/0364-Anti-Xray.patch b/patches/server-unmapped/0001/0364-Anti-Xray.patch new file mode 100644 index 0000000000..59b07f3351 --- /dev/null +++ b/patches/server-unmapped/0001/0364-Anti-Xray.patch @@ -0,0 +1,1567 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: stonar96 +Date: Mon, 20 Aug 2018 03:03:58 +0200 +Subject: [PATCH] Anti-Xray + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 1278d09f70c1e97607ef20d87a178dc252c7f723..c45493e88bf7e8811be2759ff9ac19e3fe9d938a 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -1,7 +1,9 @@ + package com.destroystokyo.paper; + ++import java.util.Arrays; + import java.util.List; + ++import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode; + import org.bukkit.Bukkit; + import org.bukkit.configuration.file.YamlConfiguration; + import org.spigotmc.SpigotWorldConfig; +@@ -461,4 +463,38 @@ public class PaperWorldConfig { + private void maxAutoSaveChunksPerTick() { + maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24); + } ++ ++ public boolean antiXray; ++ public EngineMode engineMode; ++ public int maxChunkSectionIndex; ++ public int updateRadius; ++ public boolean lavaObscures; ++ public boolean usePermission; ++ public List hiddenBlocks; ++ public List replacementBlocks; ++ private void antiXray() { ++ antiXray = getBoolean("anti-xray.enabled", false); ++ engineMode = EngineMode.getById(getInt("anti-xray.engine-mode", EngineMode.HIDE.getId())); ++ engineMode = engineMode == null ? EngineMode.HIDE : engineMode; ++ maxChunkSectionIndex = getInt("anti-xray.max-chunk-section-index", 3); ++ maxChunkSectionIndex = maxChunkSectionIndex > 15 ? 15 : maxChunkSectionIndex; ++ updateRadius = getInt("anti-xray.update-radius", 2); ++ lavaObscures = getBoolean("anti-xray.lava-obscures", false); ++ usePermission = getBoolean("anti-xray.use-permission", false); ++ hiddenBlocks = getList("anti-xray.hidden-blocks", Arrays.asList("gold_ore", "iron_ore", "coal_ore", "lapis_ore", "mossy_cobblestone", "obsidian", "chest", "diamond_ore", "redstone_ore", "clay", "emerald_ore", "ender_chest")); ++ replacementBlocks = getList("anti-xray.replacement-blocks", Arrays.asList("stone", "oak_planks")); ++ if (PaperConfig.version < 19) { ++ hiddenBlocks.remove("lit_redstone_ore"); ++ int index = replacementBlocks.indexOf("planks"); ++ if (index != -1) { ++ replacementBlocks.set(index, "oak_planks"); ++ } ++ set("anti-xray.hidden-blocks", hiddenBlocks); ++ set("anti-xray.replacement-blocks", replacementBlocks); ++ } ++ log("Anti-Xray: " + (antiXray ? "enabled" : "disabled") + " / Engine Mode: " + engineMode.getDescription() + " / Up to " + ((maxChunkSectionIndex + 1) * 16) + " blocks / Update Radius: " + updateRadius); ++ if (antiXray && usePermission) { ++ Bukkit.getLogger().warning("You have enabled permission-based Anti-Xray checking - depending on your permission plugin, this may cause performance issues"); ++ } ++ } + } +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..83a023ae018cbb79b5f151b1c7a5c8ba0c3bf1bf +--- /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.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.network.protocol.game.PacketPlayOutMapChunk; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.PlayerInteractManager; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.Chunk; ++import net.minecraft.world.level.chunk.ChunkSection; ++import net.minecraft.world.level.chunk.IChunkAccess; ++ ++public class ChunkPacketBlockController { ++ ++ public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController(); ++ ++ protected ChunkPacketBlockController() { ++ ++ } ++ ++ public IBlockData[] getPredefinedBlockData(World world, IChunkAccess chunk, ChunkSection chunkSection, boolean initializeBlocks) { ++ return null; ++ } ++ ++ public boolean shouldModify(EntityPlayer entityPlayer, Chunk chunk, int chunkSectionSelector) { ++ return false; ++ } ++ ++ public ChunkPacketInfo getChunkPacketInfo(PacketPlayOutMapChunk packetPlayOutMapChunk, Chunk chunk, int chunkSectionSelector) { ++ return null; ++ } ++ ++ public void modifyBlocks(PacketPlayOutMapChunk packetPlayOutMapChunk, ChunkPacketInfo chunkPacketInfo) { ++ packetPlayOutMapChunk.setReady(true); ++ } ++ ++ public void onBlockChange(World world, BlockPosition blockPosition, IBlockData newBlockData, IBlockData oldBlockData, int flag) { ++ ++ } ++ ++ public void onPlayerLeftClickBlock(PlayerInteractManager playerInteractManager, BlockPosition blockPosition, EnumDirection enumDirection) { ++ ++ } ++} +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..cd3b5b62d470ab6753b44f9b13dcf5522e4cbd15 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java +@@ -0,0 +1,650 @@ ++package com.destroystokyo.paper.antixray; ++ ++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.BlockPosition; ++import net.minecraft.core.EnumDirection; ++import net.minecraft.core.IRegistry; ++import net.minecraft.network.protocol.game.PacketPlayOutMapChunk; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.PlayerInteractManager; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.Chunk; ++import net.minecraft.world.level.chunk.ChunkEmpty; ++import net.minecraft.world.level.chunk.ChunkSection; ++import net.minecraft.world.level.chunk.DataPalette; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import org.bukkit.Bukkit; ++import org.bukkit.World.Environment; ++ ++import com.destroystokyo.paper.PaperWorldConfig; ++ ++public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController { ++ ++ private final Executor executor; ++ private final EngineMode engineMode; ++ private final int maxChunkSectionIndex; ++ private final int updateRadius; ++ private final boolean usePermission; ++ private final IBlockData[] predefinedBlockData; ++ private final IBlockData[] predefinedBlockDataFull; ++ private final IBlockData[] predefinedBlockDataStone; ++ private final IBlockData[] predefinedBlockDataNetherrack; ++ private final IBlockData[] predefinedBlockDataEndStone; ++ private final int[] predefinedBlockDataBitsGlobal; ++ private final int[] predefinedBlockDataBitsStoneGlobal; ++ private final int[] predefinedBlockDataBitsNetherrackGlobal; ++ private final int[] predefinedBlockDataBitsEndStoneGlobal; ++ private final boolean[] solidGlobal = new boolean[Block.REGISTRY_ID.size()]; ++ private final boolean[] obfuscateGlobal = new boolean[Block.REGISTRY_ID.size()]; ++ private final ChunkSection[] emptyNearbyChunkSections = {Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION}; ++ private final int maxBlockYUpdatePosition; ++ ++ public ChunkPacketBlockControllerAntiXray(World world, Executor executor) { ++ PaperWorldConfig paperWorldConfig = world.paperConfig; ++ engineMode = paperWorldConfig.engineMode; ++ maxChunkSectionIndex = paperWorldConfig.maxChunkSectionIndex; ++ updateRadius = paperWorldConfig.updateRadius; ++ usePermission = paperWorldConfig.usePermission; ++ ++ this.executor = executor; ++ ++ List toObfuscate; ++ ++ if (engineMode == EngineMode.HIDE) { ++ toObfuscate = paperWorldConfig.hiddenBlocks; ++ predefinedBlockData = null; ++ predefinedBlockDataFull = null; ++ predefinedBlockDataStone = new IBlockData[] {Blocks.STONE.getBlockData()}; ++ predefinedBlockDataNetherrack = new IBlockData[] {Blocks.NETHERRACK.getBlockData()}; ++ predefinedBlockDataEndStone = new IBlockData[] {Blocks.END_STONE.getBlockData()}; ++ predefinedBlockDataBitsGlobal = null; ++ predefinedBlockDataBitsStoneGlobal = new int[] {ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(Blocks.STONE.getBlockData())}; ++ predefinedBlockDataBitsNetherrackGlobal = new int[] {ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(Blocks.NETHERRACK.getBlockData())}; ++ predefinedBlockDataBitsEndStoneGlobal = new int[] {ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(Blocks.END_STONE.getBlockData())}; ++ } else { ++ toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks); ++ List predefinedBlockDataList = new LinkedList(); ++ ++ for (String id : paperWorldConfig.hiddenBlocks) { ++ Block block = IRegistry.BLOCK.getOptional(new MinecraftKey(id)).orElse(null); ++ ++ if (block != null && !block.isTileEntity()) { ++ toObfuscate.add(id); ++ predefinedBlockDataList.add(block.getBlockData()); ++ } ++ } ++ ++ // The doc of the LinkedHashSet(Collection c) constructor doesn't specify that the insertion order is the predictable iteration order of the specified Collection, although it is in the implementation ++ Set predefinedBlockDataSet = new LinkedHashSet(); ++ // Therefore addAll(Collection c) is used, which guarantees this order in the doc ++ predefinedBlockDataSet.addAll(predefinedBlockDataList); ++ predefinedBlockData = predefinedBlockDataSet.size() == 0 ? new IBlockData[] {Blocks.DIAMOND_ORE.getBlockData()} : predefinedBlockDataSet.toArray(new IBlockData[0]); ++ predefinedBlockDataFull = predefinedBlockDataSet.size() == 0 ? new IBlockData[] {Blocks.DIAMOND_ORE.getBlockData()} : predefinedBlockDataList.toArray(new IBlockData[0]); ++ predefinedBlockDataStone = null; ++ predefinedBlockDataNetherrack = null; ++ predefinedBlockDataEndStone = null; ++ predefinedBlockDataBitsGlobal = new int[predefinedBlockDataFull.length]; ++ ++ for (int i = 0; i < predefinedBlockDataFull.length; i++) { ++ predefinedBlockDataBitsGlobal[i] = ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(predefinedBlockDataFull[i]); ++ } ++ ++ predefinedBlockDataBitsStoneGlobal = null; ++ predefinedBlockDataBitsNetherrackGlobal = null; ++ predefinedBlockDataBitsEndStoneGlobal = null; ++ } ++ ++ for (String id : toObfuscate) { ++ Block block = IRegistry.BLOCK.getOptional(new MinecraftKey(id)).orElse(null); ++ ++ // Don't obfuscate air because air causes unnecessary block updates and causes block updates to fail in the void ++ if (block != null && !block.getBlockData().isAir()) { ++ // Replace all block states of a specified block ++ // No OBFHELPER for nms.BlockStateList#a() due to too many decompile errors ++ // The OBFHELPER should be getBlockDataList() ++ for (IBlockData blockData : block.getStates().a()) { ++ obfuscateGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(blockData)] = true; ++ } ++ } ++ } ++ ++ ChunkEmpty emptyChunk = new ChunkEmpty(world, new ChunkCoordIntPair(0, 0)); ++ BlockPosition zeroPos = new BlockPosition(0, 0, 0); ++ ++ for (int i = 0; i < solidGlobal.length; i++) { ++ IBlockData blockData = ChunkSection.GLOBAL_PALETTE.getObject(i); ++ ++ if (blockData != null) { ++ solidGlobal[i] = blockData.isOccluding(emptyChunk, zeroPos) ++ && blockData.getBlock() != Blocks.SPAWNER && blockData.getBlock() != Blocks.BARRIER && blockData.getBlock() != Blocks.SHULKER_BOX && blockData.getBlock() != Blocks.SLIME_BLOCK || paperWorldConfig.lavaObscures && blockData == Blocks.LAVA.getBlockData(); ++ // Comparing blockData == Blocks.LAVA.getBlockData() instead of blockData.getBlock() == Blocks.LAVA ensures that only "stationary lava" is used ++ // shulker box checks TE. ++ } ++ } ++ ++ this.maxBlockYUpdatePosition = (maxChunkSectionIndex + 1) * 16 + updateRadius - 1; ++ } ++ ++ private int getPredefinedBlockDataFullLength() { ++ return engineMode == EngineMode.HIDE ? 1 : predefinedBlockDataFull.length; ++ } ++ ++ @Override ++ public IBlockData[] getPredefinedBlockData(World world, IChunkAccess chunk, ChunkSection chunkSection, boolean initializeBlocks) { ++ // Return the block data which should be added to the data palettes so that they can be used for the obfuscation ++ if (chunkSection.getYPosition() >> 4 <= maxChunkSectionIndex) { ++ switch (engineMode) { ++ case HIDE: ++ switch (world.getWorld().getEnvironment()) { ++ case NETHER: ++ return predefinedBlockDataNetherrack; ++ case THE_END: ++ return predefinedBlockDataEndStone; ++ default: ++ return predefinedBlockDataStone; ++ } ++ default: ++ return predefinedBlockData; ++ } ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public boolean shouldModify(EntityPlayer entityPlayer, Chunk chunk, int chunkSectionSelector) { ++ return !usePermission || !entityPlayer.getBukkitEntity().hasPermission("paper.antixray.bypass"); ++ } ++ ++ @Override ++ public ChunkPacketInfoAntiXray getChunkPacketInfo(PacketPlayOutMapChunk packetPlayOutMapChunk, Chunk chunk, int chunkSectionSelector) { ++ // Return a new instance to collect data and objects in the right state while creating the chunk packet for thread safe access later ++ // Note: As of 1.14 this has to be moved later due to the chunk system. ++ ChunkPacketInfoAntiXray chunkPacketInfoAntiXray = new ChunkPacketInfoAntiXray(packetPlayOutMapChunk, chunk, chunkSectionSelector, this); ++ return chunkPacketInfoAntiXray; ++ } ++ ++ @Override ++ public void modifyBlocks(PacketPlayOutMapChunk packetPlayOutMapChunk, ChunkPacketInfo chunkPacketInfo) { ++ if (chunkPacketInfo == null) { ++ packetPlayOutMapChunk.setReady(true); ++ return; ++ } ++ ++ if (!Bukkit.isPrimaryThread()) { ++ // plugins? ++ MinecraftServer.getServer().scheduleOnMain(() -> { ++ this.modifyBlocks(packetPlayOutMapChunk, chunkPacketInfo); ++ }); ++ return; ++ } ++ ++ Chunk chunk = chunkPacketInfo.getChunk(); ++ int x = chunk.getPos().x; ++ int z = chunk.getPos().z; ++ WorldServer world = (WorldServer)chunk.world; ++ ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks( ++ (Chunk) world.getChunkIfLoadedImmediately(x - 1, z), ++ (Chunk) world.getChunkIfLoadedImmediately(x + 1, z), ++ (Chunk) world.getChunkIfLoadedImmediately(x, z - 1), ++ (Chunk) world.getChunkIfLoadedImmediately(x, z + 1)); ++ ++ executor.execute((ChunkPacketInfoAntiXray) 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 predefinedBlockDataBits = ThreadLocal.withInitial(() -> new int[getPredefinedBlockDataFullLength()]); ++ private static final ThreadLocal solid = ThreadLocal.withInitial(() -> new boolean[Block.REGISTRY_ID.size()]); ++ private static final ThreadLocal obfuscate = ThreadLocal.withInitial(() -> new boolean[Block.REGISTRY_ID.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 nextNext = ThreadLocal.withInitial(() -> new boolean[16][16]); ++ ++ public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) { ++ int[] predefinedBlockDataBits = this.predefinedBlockDataBits.get(); ++ boolean[] solid = this.solid.get(); ++ boolean[] obfuscate = this.obfuscate.get(); ++ boolean[][] current = this.current.get(); ++ boolean[][] next = this.next.get(); ++ boolean[][] nextNext = this.nextNext.get(); ++ // dataBitsReader, dataBitsWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it ++ DataBitsReader dataBitsReader = new DataBitsReader(); ++ DataBitsWriter dataBitsWriter = new DataBitsWriter(); ++ ChunkSection[] nearbyChunkSections = new ChunkSection[4]; ++ boolean[] solidTemp = null; ++ boolean[] obfuscateTemp = null; ++ dataBitsReader.setDataBits(chunkPacketInfoAntiXray.getData()); ++ dataBitsWriter.setDataBits(chunkPacketInfoAntiXray.getData()); ++ int numberOfBlocks = predefinedBlockDataBits.length; ++ // Keep the lambda expressions as simple as possible. They are used very frequently. ++ IntSupplier random = numberOfBlocks == 1 ? (() -> 0) : new IntSupplier() { ++ 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.getPredefinedObjects(chunkSectionIndex) != null) { ++ int[] predefinedBlockDataBitsTemp; ++ ++ if (chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex) == ChunkSection.GLOBAL_PALETTE) { ++ predefinedBlockDataBitsTemp = engineMode == EngineMode.HIDE ? chunkPacketInfoAntiXray.getChunk().world.getWorld().getEnvironment() == Environment.NETHER ? predefinedBlockDataBitsNetherrackGlobal : chunkPacketInfoAntiXray.getChunk().world.getWorld().getEnvironment() == Environment.THE_END ? predefinedBlockDataBitsEndStoneGlobal : predefinedBlockDataBitsStoneGlobal : predefinedBlockDataBitsGlobal; ++ } else { ++ // If it's this.predefinedBlockData, use this.predefinedBlockDataFull instead ++ IBlockData[] predefinedBlockDataFull = chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex) == predefinedBlockData ? this.predefinedBlockDataFull : chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex); ++ predefinedBlockDataBitsTemp = predefinedBlockDataBits; ++ ++ for (int i = 0; i < predefinedBlockDataBitsTemp.length; i++) { ++ predefinedBlockDataBitsTemp[i] = chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex).getOrCreateIdFor(predefinedBlockDataFull[i]); ++ } ++ } ++ ++ dataBitsWriter.setIndex(chunkPacketInfoAntiXray.getDataBitsIndex(chunkSectionIndex)); ++ ++ // Check if the chunk section below was not obfuscated ++ if (chunkSectionIndex == 0 || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex - 1) || chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex - 1) == null) { ++ // If so, initialize some stuff ++ dataBitsReader.setBitsPerObject(chunkPacketInfoAntiXray.getBitsPerObject(chunkSectionIndex)); ++ dataBitsReader.setIndex(chunkPacketInfoAntiXray.getDataBitsIndex(chunkSectionIndex)); ++ solidTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex), solid, solidGlobal); ++ obfuscateTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex), obfuscate, obfuscateGlobal); ++ // Read the blocks of the upper layer of the chunk section below if it exists ++ ChunkSection belowChunkSection = null; ++ boolean skipFirstLayer = chunkSectionIndex == 0 || (belowChunkSection = chunkPacketInfoAntiXray.getChunk().getSections()[chunkSectionIndex - 1]) == Chunk.EMPTY_CHUNK_SECTION; ++ ++ for (int z = 0; z < 16; z++) { ++ for (int x = 0; x < 16; x++) { ++ current[z][x] = true; ++ next[z][x] = skipFirstLayer || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(belowChunkSection.getType(x, 15, z))]; ++ } ++ } ++ ++ // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section ++ dataBitsWriter.setBitsPerObject(0); ++ obfuscateLayer(-1, dataBitsReader, dataBitsWriter, solidTemp, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, emptyNearbyChunkSections, random); ++ } ++ ++ dataBitsWriter.setBitsPerObject(chunkPacketInfoAntiXray.getBitsPerObject(chunkSectionIndex)); ++ nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? Chunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? Chunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? Chunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? Chunk.EMPTY_CHUNK_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; ++ obfuscateLayer(y, dataBitsReader, dataBitsWriter, solidTemp, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ ++ // Check if the chunk section above doesn't need obfuscation ++ if (chunkSectionIndex == maxChunkSectionIndex || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex + 1) || chunkPacketInfoAntiXray.getPredefinedObjects(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 ++ ChunkSection aboveChunkSection; ++ ++ if (chunkSectionIndex != 15 && (aboveChunkSection = chunkPacketInfoAntiXray.getChunk().getSections()[chunkSectionIndex + 1]) != Chunk.EMPTY_CHUNK_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 (!solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(aboveChunkSection.getType(x, 0, z))]) { ++ current[z][x] = true; ++ } ++ } ++ } ++ ++ // There is nothing to read anymore ++ dataBitsReader.setBitsPerObject(0); ++ solid[0] = true; ++ obfuscateLayer(15, dataBitsReader, dataBitsWriter, solid, obfuscateTemp, predefinedBlockDataBitsTemp, 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 ++ dataBitsReader.setBitsPerObject(chunkPacketInfoAntiXray.getBitsPerObject(chunkSectionIndex + 1)); ++ dataBitsReader.setIndex(chunkPacketInfoAntiXray.getDataBitsIndex(chunkSectionIndex + 1)); ++ solidTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex + 1), solid, solidGlobal); ++ obfuscateTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal); ++ boolean[][] temp = current; ++ current = next; ++ next = nextNext; ++ nextNext = temp; ++ obfuscateLayer(15, dataBitsReader, dataBitsWriter, solidTemp, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ ++ dataBitsWriter.finish(); ++ } ++ } ++ ++ chunkPacketInfoAntiXray.getPacketPlayOutMapChunk().setReady(true); ++ } ++ ++ private void obfuscateLayer(int y, DataBitsReader dataBitsReader, DataBitsWriter dataBitsWriter, boolean[] solid, boolean[] obfuscate, int[] predefinedBlockDataBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, ChunkSection[] nearbyChunkSections, IntSupplier random) { ++ // First block of first line ++ int dataBits = dataBitsReader.read(); ++ ++ if (nextNext[0][0] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[0][1] = true; ++ next[1][0] = true; ++ } else { ++ if (nearbyChunkSections[2] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getType(0, y, 15))] || nearbyChunkSections[0] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getType(15, y, 0))] || current[0][0]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[0][0] = true; ++ } ++ ++ // First line ++ for (int x = 1; x < 15; x++) { ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[0][x] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[0][x - 1] = true; ++ next[0][x + 1] = true; ++ next[1][x] = true; ++ } else { ++ if (nearbyChunkSections[2] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getType(x, y, 15))] || current[0][x]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[0][x] = true; ++ } ++ } ++ ++ // Last block of first line ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[0][15] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[0][14] = true; ++ next[1][15] = true; ++ } else { ++ if (nearbyChunkSections[2] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getType(15, y, 15))] || nearbyChunkSections[1] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getType(0, y, 0))] || current[0][15]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[0][15] = true; ++ } ++ ++ // All inner lines ++ for (int z = 1; z < 15; z++) { ++ // First block ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[z][0] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[z][1] = true; ++ next[z - 1][0] = true; ++ next[z + 1][0] = true; ++ } else { ++ if (nearbyChunkSections[0] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getType(15, y, z))] || current[z][0]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[z][0] = true; ++ } ++ ++ // All inner blocks ++ for (int x = 1; x < 15; x++) { ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[z][x] = !solid[dataBits]) { ++ dataBitsWriter.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]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[z][x] = true; ++ } ++ } ++ ++ // Last block ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[z][15] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[z][14] = true; ++ next[z - 1][15] = true; ++ next[z + 1][15] = true; ++ } else { ++ if (nearbyChunkSections[1] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getType(0, y, z))] || current[z][15]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[z][15] = true; ++ } ++ } ++ ++ // First block of last line ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[15][0] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[15][1] = true; ++ next[14][0] = true; ++ } else { ++ if (nearbyChunkSections[3] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getType(0, y, 0))] || nearbyChunkSections[0] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getType(15, y, 15))] || current[15][0]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[15][0] = true; ++ } ++ ++ // Last line ++ for (int x = 1; x < 15; x++) { ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[15][x] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[15][x - 1] = true; ++ next[15][x + 1] = true; ++ next[14][x] = true; ++ } else { ++ if (nearbyChunkSections[3] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getType(x, y, 0))] || current[15][x]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[15][x] = true; ++ } ++ } ++ ++ // Last block of last line ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[15][15] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[15][14] = true; ++ next[14][15] = true; ++ } else { ++ if (nearbyChunkSections[3] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getType(15, y, 0))] || nearbyChunkSections[1] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getType(0, y, 15))] || current[15][15]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[15][15] = true; ++ } ++ } ++ ++ private boolean[] readDataPalette(DataPalette dataPalette, boolean[] temp, boolean[] global) { ++ if (dataPalette == ChunkSection.GLOBAL_PALETTE) { ++ return global; ++ } ++ ++ IBlockData blockData; ++ ++ for (int i = 0; (blockData = dataPalette.getObject(i)) != null; i++) { ++ temp[i] = global[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(blockData)]; ++ } ++ ++ return temp; ++ } ++ ++ @Override ++ public void onBlockChange(World world, BlockPosition blockPosition, IBlockData newBlockData, IBlockData oldBlockData, int flag) { ++ if (oldBlockData != null && solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(oldBlockData)] && !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(newBlockData)] && blockPosition.getY() <= maxBlockYUpdatePosition) { ++ updateNearbyBlocks(world, blockPosition); ++ } ++ } ++ ++ @Override ++ public void onPlayerLeftClickBlock(PlayerInteractManager playerInteractManager, BlockPosition blockPosition, EnumDirection enumDirection) { ++ if (blockPosition.getY() <= maxBlockYUpdatePosition) { ++ updateNearbyBlocks(playerInteractManager.world, blockPosition); ++ } ++ } ++ ++ private void updateNearbyBlocks(World world, BlockPosition blockPosition) { ++ if (updateRadius >= 2) { ++ BlockPosition temp = blockPosition.west(); ++ updateBlock(world, temp); ++ updateBlock(world, temp.west()); ++ updateBlock(world, temp.down()); ++ updateBlock(world, temp.up()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.east()); ++ updateBlock(world, temp.east()); ++ updateBlock(world, temp.down()); ++ updateBlock(world, temp.up()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.down()); ++ updateBlock(world, temp.down()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.up()); ++ updateBlock(world, temp.up()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.north()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp = blockPosition.south()); ++ updateBlock(world, temp.south()); ++ } else if (updateRadius == 1) { ++ updateBlock(world, blockPosition.west()); ++ updateBlock(world, blockPosition.east()); ++ updateBlock(world, blockPosition.down()); ++ updateBlock(world, blockPosition.up()); ++ updateBlock(world, blockPosition.north()); ++ updateBlock(world, blockPosition.south()); ++ } else { ++ // Do nothing if updateRadius <= 0 (test mode) ++ } ++ } ++ ++ private void updateBlock(World world, BlockPosition blockPosition) { ++ IBlockData blockData = world.getTypeIfLoaded(blockPosition); ++ ++ if (blockData != null && obfuscateGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(blockData)]) { ++ // world.notify(blockPosition, blockData, blockData, 3); ++ ((WorldServer)world).getChunkProvider().flagDirty(blockPosition); // We only need to re-send to client ++ } ++ } ++ ++ public enum EngineMode { ++ ++ HIDE(1, "hide ores"), ++ OBFUSCATE(2, "obfuscate"); ++ ++ private final int id; ++ private final String description; ++ ++ EngineMode(int id, String description) { ++ this.id = id; ++ this.description = description; ++ } ++ ++ public static EngineMode getById(int id) { ++ for (EngineMode engineMode : values()) { ++ if (engineMode.id == id) { ++ return engineMode; ++ } ++ } ++ ++ return null; ++ } ++ ++ public int getId() { ++ return id; ++ } ++ ++ public String getDescription() { ++ return description; ++ } ++ } ++} +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..22c9adefc3e51e9e4b8d611a40d1497d2a16a8d2 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java +@@ -0,0 +1,81 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.network.protocol.game.PacketPlayOutMapChunk; ++import net.minecraft.world.level.chunk.Chunk; ++import net.minecraft.world.level.chunk.DataPalette; ++ ++public class ChunkPacketInfo { ++ ++ private final PacketPlayOutMapChunk packetPlayOutMapChunk; ++ private final Chunk chunk; ++ private final int chunkSectionSelector; ++ private byte[] data; ++ private final int[] bitsPerObject = new int[16]; ++ private final Object[] dataPalettes = new Object[16]; ++ private final int[] dataBitsIndexes = new int[16]; ++ private final Object[][] predefinedObjects = new Object[16][]; ++ ++ public ChunkPacketInfo(PacketPlayOutMapChunk packetPlayOutMapChunk, Chunk chunk, int chunkSectionSelector) { ++ this.packetPlayOutMapChunk = packetPlayOutMapChunk; ++ this.chunk = chunk; ++ this.chunkSectionSelector = chunkSectionSelector; ++ } ++ ++ public PacketPlayOutMapChunk getPacketPlayOutMapChunk() { ++ return packetPlayOutMapChunk; ++ } ++ ++ public Chunk getChunk() { ++ return chunk; ++ } ++ ++ public int getChunkSectionSelector() { ++ return chunkSectionSelector; ++ } ++ ++ public byte[] getData() { ++ return data; ++ } ++ ++ public void setData(byte[] data) { ++ this.data = data; ++ } ++ ++ public int getBitsPerObject(int chunkSectionIndex) { ++ return bitsPerObject[chunkSectionIndex]; ++ } ++ ++ public void setBitsPerObject(int chunkSectionIndex, int bitsPerObject) { ++ this.bitsPerObject[chunkSectionIndex] = bitsPerObject; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public DataPalette getDataPalette(int chunkSectionIndex) { ++ return (DataPalette) dataPalettes[chunkSectionIndex]; ++ } ++ ++ public void setDataPalette(int chunkSectionIndex, DataPalette dataPalette) { ++ dataPalettes[chunkSectionIndex] = dataPalette; ++ } ++ ++ public int getDataBitsIndex(int chunkSectionIndex) { ++ return dataBitsIndexes[chunkSectionIndex]; ++ } ++ ++ public void setDataBitsIndex(int chunkSectionIndex, int dataBitsIndex) { ++ dataBitsIndexes[chunkSectionIndex] = dataBitsIndex; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public T[] getPredefinedObjects(int chunkSectionIndex) { ++ return (T[]) predefinedObjects[chunkSectionIndex]; ++ } ++ ++ public void setPredefinedObjects(int chunkSectionIndex, T[] predefinedObjects) { ++ this.predefinedObjects[chunkSectionIndex] = predefinedObjects; ++ } ++ ++ public boolean isWritten(int chunkSectionIndex) { ++ return bitsPerObject[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..4b64964e52b11bea4d2c0d0f64f55ad08d2189be +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java +@@ -0,0 +1,30 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.network.protocol.game.PacketPlayOutMapChunk; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.Chunk; ++ ++public final class ChunkPacketInfoAntiXray extends ChunkPacketInfo implements Runnable { ++ ++ private Chunk[] nearbyChunks; ++ private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray; ++ ++ public ChunkPacketInfoAntiXray(PacketPlayOutMapChunk packetPlayOutMapChunk, Chunk chunk, int chunkSectionSelector, ++ ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) { ++ super(packetPlayOutMapChunk, chunk, chunkSectionSelector); ++ this.chunkPacketBlockControllerAntiXray = chunkPacketBlockControllerAntiXray; ++ } ++ ++ public Chunk[] getNearbyChunks() { ++ return nearbyChunks; ++ } ++ ++ public void setNearbyChunks(Chunk... nearbyChunks) { ++ this.nearbyChunks = nearbyChunks; ++ } ++ ++ @Override ++ public void run() { ++ chunkPacketBlockControllerAntiXray.obfuscate(this); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..298ea423084dbcc1b61f991bcd82b8ae51bf0977 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java +@@ -0,0 +1,51 @@ ++package com.destroystokyo.paper.antixray; ++ ++public final class DataBitsReader { ++ ++ private byte[] dataBits; ++ private int bitsPerObject; ++ private int mask; ++ private int longInDataBitsIndex; ++ private int bitInLongIndex; ++ private long current; ++ ++ public void setDataBits(byte[] dataBits) { ++ this.dataBits = dataBits; ++ } ++ ++ public void setBitsPerObject(int bitsPerObject) { ++ this.bitsPerObject = bitsPerObject; ++ mask = (1 << bitsPerObject) - 1; ++ } ++ ++ public void setIndex(int index) { ++ this.longInDataBitsIndex = index; ++ bitInLongIndex = 0; ++ init(); ++ } ++ ++ private void init() { ++ if (dataBits.length > longInDataBitsIndex + 7) { ++ current = ((((long) dataBits[longInDataBitsIndex]) << 56) ++ | (((long) dataBits[longInDataBitsIndex + 1] & 0xff) << 48) ++ | (((long) dataBits[longInDataBitsIndex + 2] & 0xff) << 40) ++ | (((long) dataBits[longInDataBitsIndex + 3] & 0xff) << 32) ++ | (((long) dataBits[longInDataBitsIndex + 4] & 0xff) << 24) ++ | (((long) dataBits[longInDataBitsIndex + 5] & 0xff) << 16) ++ | (((long) dataBits[longInDataBitsIndex + 6] & 0xff) << 8) ++ | (((long) dataBits[longInDataBitsIndex + 7] & 0xff))); ++ } ++ } ++ ++ public int read() { ++ if (bitInLongIndex + bitsPerObject > 64) { ++ bitInLongIndex = 0; ++ longInDataBitsIndex += 8; ++ init(); ++ } ++ ++ int value = (int) (current >>> bitInLongIndex) & mask; ++ bitInLongIndex += bitsPerObject; ++ return value; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..333763936897befda5bb6c077944d2667f922799 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java +@@ -0,0 +1,79 @@ ++package com.destroystokyo.paper.antixray; ++ ++public final class DataBitsWriter { ++ ++ private byte[] dataBits; ++ private int bitsPerObject; ++ private long mask; ++ private int longInDataBitsIndex; ++ private int bitInLongIndex; ++ private long current; ++ private boolean dirty; ++ ++ public void setDataBits(byte[] dataBits) { ++ this.dataBits = dataBits; ++ } ++ ++ public void setBitsPerObject(int bitsPerObject) { ++ this.bitsPerObject = bitsPerObject; ++ mask = (1 << bitsPerObject) - 1; ++ } ++ ++ public void setIndex(int index) { ++ this.longInDataBitsIndex = index; ++ bitInLongIndex = 0; ++ init(); ++ } ++ ++ private void init() { ++ if (dataBits.length > longInDataBitsIndex + 7) { ++ current = ((((long) dataBits[longInDataBitsIndex]) << 56) ++ | (((long) dataBits[longInDataBitsIndex + 1] & 0xff) << 48) ++ | (((long) dataBits[longInDataBitsIndex + 2] & 0xff) << 40) ++ | (((long) dataBits[longInDataBitsIndex + 3] & 0xff) << 32) ++ | (((long) dataBits[longInDataBitsIndex + 4] & 0xff) << 24) ++ | (((long) dataBits[longInDataBitsIndex + 5] & 0xff) << 16) ++ | (((long) dataBits[longInDataBitsIndex + 6] & 0xff) << 8) ++ | (((long) dataBits[longInDataBitsIndex + 7] & 0xff))); ++ } ++ ++ dirty = false; ++ } ++ ++ public void finish() { ++ if (dirty && dataBits.length > longInDataBitsIndex + 7) { ++ dataBits[longInDataBitsIndex] = (byte) (current >> 56 & 0xff); ++ dataBits[longInDataBitsIndex + 1] = (byte) (current >> 48 & 0xff); ++ dataBits[longInDataBitsIndex + 2] = (byte) (current >> 40 & 0xff); ++ dataBits[longInDataBitsIndex + 3] = (byte) (current >> 32 & 0xff); ++ dataBits[longInDataBitsIndex + 4] = (byte) (current >> 24 & 0xff); ++ dataBits[longInDataBitsIndex + 5] = (byte) (current >> 16 & 0xff); ++ dataBits[longInDataBitsIndex + 6] = (byte) (current >> 8 & 0xff); ++ dataBits[longInDataBitsIndex + 7] = (byte) (current & 0xff); ++ } ++ } ++ ++ public void write(int value) { ++ if (bitInLongIndex + bitsPerObject > 64) { ++ finish(); ++ bitInLongIndex = 0; ++ longInDataBitsIndex += 8; ++ init(); ++ } ++ ++ current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex; ++ dirty = true; ++ bitInLongIndex += bitsPerObject; ++ } ++ ++ public void skip() { ++ bitInLongIndex += bitsPerObject; ++ ++ if (bitInLongIndex > 64) { ++ finish(); ++ bitInLongIndex = bitsPerObject; ++ longInDataBitsIndex += 8; ++ init(); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +index a7d10d124021f3427f23fcd533f885367b64515c..3047cf8c4ec1b664d6b790f18d2b1657e4b00435 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +@@ -1,5 +1,6 @@ + package net.minecraft.network.protocol.game; + ++import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray - Add chunk packet info + import com.google.common.collect.Lists; + import io.netty.buffer.ByteBuf; + import io.netty.buffer.Unpooled; +@@ -16,6 +17,7 @@ import net.minecraft.network.protocol.Packet; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.block.entity.TileEntity; + import net.minecraft.world.level.block.entity.TileEntitySkull; ++import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.level.chunk.BiomeStorage; + import net.minecraft.world.level.chunk.Chunk; + import net.minecraft.world.level.chunk.ChunkSection; +@@ -33,7 +35,13 @@ public class PacketPlayOutMapChunk implements Packet { + private List g; + private boolean h; + +- public PacketPlayOutMapChunk() {} ++ // Paper start - Async-Anti-Xray - Set the ready flag to true ++ private volatile boolean ready; // Paper - Async-Anti-Xray - Ready flag for the network manager ++ public PacketPlayOutMapChunk() { ++ this.ready = true; ++ } ++ // Paper end ++ + // Paper start + private final java.util.List extraPackets = new java.util.ArrayList<>(); + private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750); +@@ -43,7 +51,11 @@ public class PacketPlayOutMapChunk implements Packet { + return extraPackets; + } + // Paper end +- public PacketPlayOutMapChunk(Chunk chunk, int i) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated public PacketPlayOutMapChunk(Chunk chunk, int i) { this(chunk, i, true); } // Notice for updates: Please make sure this constructor isn't used anywhere ++ public PacketPlayOutMapChunk(Chunk chunk, int i, boolean modifyBlocks) { ++ ChunkPacketInfo chunkPacketInfo = modifyBlocks ? chunk.world.chunkPacketBlockController.getChunkPacketInfo(this, chunk, i) : null; ++ // Paper end + ChunkCoordIntPair chunkcoordintpair = chunk.getPos(); + + this.a = chunkcoordintpair.x; +@@ -66,7 +78,12 @@ public class PacketPlayOutMapChunk implements Packet { + } + + this.f = new byte[this.a(chunk, i)]; +- this.c = this.a(new PacketDataSerializer(this.j()), chunk, i); ++ // Paper start - Anti-Xray - Add chunk packet info ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setData(this.getData()); ++ } ++ this.c = this.writeChunk(new PacketDataSerializer(this.j()), chunk, i, chunkPacketInfo); ++ // Paper end + this.g = Lists.newArrayList(); + iterator = chunk.getTileEntities().entrySet().iterator(); + int totalTileEntities = 0; // Paper +@@ -93,8 +110,19 @@ public class PacketPlayOutMapChunk implements Packet { + this.g.add(nbttagcompound); + } + } ++ chunk.world.chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks ++ } + ++ // Paper start - Async-Anti-Xray - Getter and Setter for the ready flag ++ @Override ++ public boolean isReady() { ++ return this.ready; ++ } ++ ++ public void setReady(boolean ready) { ++ this.ready = ready; + } ++ // Paper end + + @Override + public void a(PacketDataSerializer packetdataserializer) throws IOException { +@@ -160,8 +188,12 @@ public class PacketPlayOutMapChunk implements Packet { + return bytebuf; + } + +- public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, int chunkSectionSelector) { return this.a(packetDataSerializer, chunk, chunkSectionSelector); } // Paper - OBFHELPER +- public int a(PacketDataSerializer packetdataserializer, Chunk chunk, int i) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, int chunkSectionSelector) { return this.a(packetDataSerializer, chunk, chunkSectionSelector); } // OBFHELPER // Notice for updates: Please make sure this method isn't used anywhere ++ @Deprecated public int a(PacketDataSerializer packetdataserializer, Chunk chunk, int i) { return this.writeChunk(packetdataserializer, chunk, i, null); } // Notice for updates: Please make sure this method isn't used anywhere ++ public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, int chunkSectionSelector, ChunkPacketInfo chunkPacketInfo) { return this.a(packetDataSerializer, chunk, chunkSectionSelector, chunkPacketInfo); } // OBFHELPER ++ public int a(PacketDataSerializer packetdataserializer, Chunk chunk, int i, ChunkPacketInfo chunkPacketInfo) { ++ // Paper end + int j = 0; + ChunkSection[] achunksection = chunk.getSections(); + int k = 0; +@@ -171,7 +203,7 @@ public class PacketPlayOutMapChunk implements Packet { + + if (chunksection != Chunk.a && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { + j |= 1 << k; +- chunksection.b(packetdataserializer); ++ chunksection.writeChunkSection(packetdataserializer, chunkPacketInfo); // Paper - Anti-Xray - Add chunk packet info + } + } + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 1f32ab230d650bb5f652efbacdd5e4b90dc4de89..71c2792d7eede35485cc36ac929cf295bcd4646b 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -656,7 +656,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + this.g(chunkcoordintpair); +- return Either.left(new ProtoChunk(chunkcoordintpair, ChunkConverter.a)); ++ return Either.left(new ProtoChunk(chunkcoordintpair, ChunkConverter.a, this.world)); // Paper - Anti-Xray - Add parameter + }, this.executor); + } + +@@ -1396,9 +1396,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + } + ++ private final void sendChunk(EntityPlayer entityplayer, Packet[] apacket, Chunk chunk) { this.a(entityplayer, apacket, chunk); } // Paper - OBFHELPER + private void a(EntityPlayer entityplayer, Packet[] apacket, Chunk chunk) { + if (apacket[0] == null) { +- apacket[0] = new PacketPlayOutMapChunk(chunk, 65535); ++ apacket[0] = new PacketPlayOutMapChunk(chunk, 65535, chunk.world.chunkPacketBlockController.shouldModify(entityplayer, chunk, 65535)); // Paper - Anti-Xray - Bypass + apacket[1] = new PacketPlayOutLightUpdate(chunk.getPos(), this.lightEngine, true); + } + +diff --git a/src/main/java/net/minecraft/server/level/PlayerInteractManager.java b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +index 2892189bbacb3307cfa7c2b3cbb9cd56eda9ef3c..7c7425f2312882540947f0fc528d123933e8fd98 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerInteractManager.java ++++ b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +@@ -308,6 +308,8 @@ public class PlayerInteractManager { + } + + } ++ ++ this.world.chunkPacketBlockController.onPlayerLeftClickBlock(this, blockposition, enumdirection); // Paper - Anti-Xray + } + + public void a(BlockPosition blockposition, PacketPlayInBlockDig.EnumPlayerDigType packetplayinblockdig_enumplayerdigtype, String s) { +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 735da5729c16940e3d8877f32a40342b9d1e989d..caf3d4df460d2d6dad6e68a68e1256e3603e3891 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -210,7 +210,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + // Add env and gen to constructor, WorldData -> WorldDataServer + public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { +- super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env); ++ super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor + this.pvpMode = minecraftserver.getPVP(); + convertable = convertable_conversionsession; + uuid = WorldUUID.getUUID(convertable_conversionsession.folder.toFile()); +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 2d507f524152eb92a495d49a7a45a19aec522657..1e7e64fdd058e7fe2bb96825d7d5170ccc4c0b50 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -2,6 +2,8 @@ package net.minecraft.world.level; + + import co.aikar.timings.Timing; + import co.aikar.timings.Timings; ++import com.destroystokyo.paper.antixray.ChunkPacketBlockController; // Paper - Anti-Xray ++import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray; // Paper - Anti-Xray + import com.destroystokyo.paper.event.server.ServerExceptionEvent; + import com.destroystokyo.paper.exception.ServerInternalException; + import com.google.common.base.MoreObjects; +@@ -144,6 +146,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot + + public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper ++ public final ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + + public final co.aikar.timings.WorldTimingsHandler timings; // Paper + public static BlockPosition lastPhysicsProblem; // Spigot +@@ -165,9 +168,10 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return typeKey; + } + +- protected World(WorldDataMutable worlddatamutable, ResourceKey resourcekey, final DimensionManager dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) { ++ protected World(WorldDataMutable worlddatamutable, ResourceKey resourcekey, final DimensionManager dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.WorldDataServer) worlddatamutable).getName()); // Spigot + this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.WorldDataServer) worlddatamutable).getName(), this.spigotConfig); // Paper ++ this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.generator = gen; + this.world = new CraftWorld((WorldServer) this, gen, env); + this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit +@@ -433,6 +437,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + // CraftBukkit end + + IBlockData iblockdata1 = chunk.setType(blockposition, iblockdata, (i & 64) != 0, (i & 1024) == 0); // CraftBukkit custom NO_PLACE flag ++ this.chunkPacketBlockController.onBlockChange(this, blockposition, iblockdata, iblockdata1, i); // 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/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index 2690c44eaae193a259fe195c95e59d07d5e1cc5a..3fdce0e6fa34eb4b1eafc618068a3fb06abd5ec1 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -465,7 +465,7 @@ public class Chunk implements IChunkAccess { + return null; + } + +- chunksection = new ChunkSection(j >> 4 << 4); ++ chunksection = new ChunkSection(j >> 4 << 4, this, this.world, true); // Paper - Anti-Xray - Add parameters + this.sections[j >> 4] = chunksection; + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkEmpty.java b/src/main/java/net/minecraft/world/level/chunk/ChunkEmpty.java +index 89efd0b68b04457e1cd617dcc8bb1a6ea1c4717c..9fb8d20e9e1a8cc716c32a100b1d70e90f385eca 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkEmpty.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkEmpty.java +@@ -8,6 +8,7 @@ import net.minecraft.SystemUtils; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.IRegistry; + import net.minecraft.data.worldgen.biome.BiomeRegistry; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.PlayerChunk; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.ChunkCoordIntPair; +@@ -28,7 +29,7 @@ public class ChunkEmpty extends Chunk { + }); + + public ChunkEmpty(World world, ChunkCoordIntPair chunkcoordintpair) { +- super(world, chunkcoordintpair, new BiomeStorage(world.r().b(IRegistry.ay), ChunkEmpty.b)); ++ super(world, chunkcoordintpair, new BiomeStorage(MinecraftServer.getServer().getCustomRegistry().b(IRegistry.ay), ChunkEmpty.b)); // Paper - world isnt ready yet for anti xray use here, use server singleton for registry + } + + // Paper start +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +index eea4a30428293eaf7afbe303a37adec60b44c2b4..0b4e346daaea91565fde2f789fafa8b431a7b042 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +@@ -1,9 +1,11 @@ + package net.minecraft.world.level.chunk; + + import java.util.function.Predicate; ++import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray - Add chunk packet info + import javax.annotation.Nullable; + import net.minecraft.nbt.GameProfileSerializer; + import net.minecraft.network.PacketDataSerializer; ++import net.minecraft.world.level.World; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.IBlockData; +@@ -18,16 +20,22 @@ public class ChunkSection { + private short e; + final DataPaletteBlock blockIds; // Paper - package-private + +- public ChunkSection(int i) { +- this(i, (short) 0, (short) 0, (short) 0); ++ // Paper start - Anti-Xray - Add parameters ++ @Deprecated public ChunkSection(int i) { this(i, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere ++ public ChunkSection(int i, IChunkAccess chunk, World world, boolean initializeBlocks) { ++ this(i, (short) 0, (short) 0, (short) 0, chunk, world, initializeBlocks); ++ // Paper end + } + +- public ChunkSection(int i, short short0, short short1, short short2) { ++ // Paper start - Anti-Xray - Add parameters ++ @Deprecated public ChunkSection(int i, short short0, short short1, short short2) { this(i, short0, short1, short2, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere ++ public ChunkSection(int i, short short0, short short1, short short2, IChunkAccess chunk, World world, boolean initializeBlocks) { ++ // Paper end + this.yPos = i; + this.nonEmptyBlockCount = short0; + this.tickingBlockCount = short1; + this.e = short2; +- this.blockIds = new DataPaletteBlock<>(ChunkSection.GLOBAL_PALETTE, Block.REGISTRY_ID, GameProfileSerializer::c, GameProfileSerializer::a, Blocks.AIR.getBlockData()); ++ this.blockIds = new DataPaletteBlock<>(ChunkSection.GLOBAL_PALETTE, Block.REGISTRY_ID, GameProfileSerializer::c, GameProfileSerializer::a, Blocks.AIR.getBlockData(), world == null ? null : world.chunkPacketBlockController.getPredefinedBlockData(world, chunk, this, initializeBlocks), initializeBlocks); // Paper - Anti-Xray - Add predefined block data + } + + public final IBlockData getType(int i, int j, int k) { // Paper +@@ -139,10 +147,14 @@ public class ChunkSection { + return this.blockIds; + } + +- public void writeChunkSection(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // Paper - OBFHELPER +- public void b(PacketDataSerializer packetdataserializer) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated public final void writeChunkSection(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // OBFHELPER // Notice for updates: Please make sure this method isn't used anywhere ++ @Deprecated public final void b(PacketDataSerializer packetdataserializer) { this.writeChunkSection(packetdataserializer, null); } // Notice for updates: Please make sure this method isn't used anywhere ++ public final void writeChunkSection(PacketDataSerializer packetDataSerializer, ChunkPacketInfo chunkPacketInfo) { this.b(packetDataSerializer, chunkPacketInfo); } // OBFHELPER ++ public void b(PacketDataSerializer packetdataserializer, ChunkPacketInfo chunkPacketInfo) { ++ // Paper end + packetdataserializer.writeShort(this.nonEmptyBlockCount); +- this.blockIds.b(packetdataserializer); ++ this.blockIds.writeDataPaletteBlock(packetdataserializer, chunkPacketInfo, this.yPos >> 4); // Paper - Anti-Xray - Add chunk packet info + } + + public int j() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +index cc0c5995dc3840ce66ea849849f7c37555d3b5e6..68d53a51acc9790b9cda20ec4d2ec6edd1baac1a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java ++++ b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +@@ -1,6 +1,7 @@ + package net.minecraft.world.level.chunk; + + import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; ++import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray - Add chunk packet info + import java.util.Arrays; + import java.util.Objects; + import java.util.concurrent.locks.ReentrantLock; +@@ -27,6 +28,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { + private final Function e; + private final Function f; + private final T g; ++ private final T[] predefinedObjects; // Paper - Anti-Xray - Add predefined objects + protected DataBits a; public final DataBits getDataBits() { return this.a; } // Paper - OBFHELPER + private DataPalette h; private DataPalette getDataPalette() { return this.h; } // Paper - OBFHELPER + private int i; private int getBitsPerObject() { return this.i; } // Paper - OBFHELPER +@@ -51,14 +53,47 @@ public class DataPaletteBlock implements DataPaletteExpandable { + //this.j.unlock(); // Paper - disable this + } + +- public DataPaletteBlock(DataPalette datapalette, RegistryBlockID registryblockid, Function function, Function function1, T t0) { ++ // Paper start - Anti-Xray - Add predefined objects ++ @Deprecated public DataPaletteBlock(DataPalette datapalette, RegistryBlockID registryblockid, Function function, Function function1, T t0) { this(datapalette, registryblockid, function, function1, t0, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere ++ public DataPaletteBlock(DataPalette datapalette, RegistryBlockID registryblockid, Function function, Function function1, T t0, T[] predefinedObjects, boolean initialize) { ++ // Paper end + this.b = datapalette; + this.d = registryblockid; + this.e = function; + this.f = function1; + this.g = t0; +- this.b(4); ++ // Paper start - Anti-Xray - Add predefined objects ++ this.predefinedObjects = predefinedObjects; ++ ++ if (initialize) { ++ if (predefinedObjects == null) { ++ // Default ++ this.initialize(4); ++ } else { ++ // MathHelper.d() is trailingBits(roundCeilPow2(n)), alternatively; (int)ceil(log2(n)); however it's trash, use numberOfLeadingZeros instead ++ // Count the bits of the maximum array index to initialize a data palette with enough space from the beginning ++ // The length of the array is used because air is also added to the data palette from the beginning ++ // Start with at least 4 ++ int maxIndex = predefinedObjects.length >> 4; ++ int bitCount = (32 - Integer.numberOfLeadingZeros(Math.max(16, maxIndex) - 1)); ++ ++ // Initialize with at least 15 free indixes ++ this.initialize((1 << bitCount) - predefinedObjects.length < 16 ? bitCount + 1 : bitCount); ++ this.addPredefinedObjects(); ++ } ++ } ++ // Paper end ++ } ++ ++ // Paper start - Anti-Xray - Add predefined objects ++ private void addPredefinedObjects() { ++ if (this.predefinedObjects != null && this.getDataPalette() != this.getDataPaletteGlobal()) { ++ for (int i = 0; i < this.predefinedObjects.length; i++) { ++ this.getDataPalette().getOrCreateIdFor(this.predefinedObjects[i]); ++ } ++ } + } ++ // Paper end + + private static int b(int i, int j, int k) { + return j << 8 | k << 4 | i; +@@ -93,6 +128,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { + + int j; + ++ this.addPredefinedObjects(); // Paper - Anti-Xray - Add predefined objects + for (j = 0; j < databits.b(); ++j) { + T t1 = datapalette.a(databits.a(j)); + +@@ -142,24 +178,38 @@ public class DataPaletteBlock implements DataPaletteExpandable { + return t0 == null ? this.g : t0; + } + +- public void writeDataPaletteBlock(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // Paper - OBFHELPER +- public synchronized void b(PacketDataSerializer packetdataserializer) { // Paper - synchronize ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated public void writeDataPaletteBlock(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // OBFHELPER // Notice for updates: Please make sure this method isn't used anywhere ++ @Deprecated public void b(PacketDataSerializer packetdataserializer) { this.writeDataPaletteBlock(packetdataserializer, null, 0); } // Notice for updates: Please make sure this method isn't used anywhere ++ public void writeDataPaletteBlock(PacketDataSerializer packetDataSerializer, ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { this.b(packetDataSerializer, chunkPacketInfo, chunkSectionIndex); } // OBFHELPER ++ public synchronized void b(PacketDataSerializer packetdataserializer, ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { // Paper - synchronize ++ // Paper end + this.a(); + packetdataserializer.writeByte(this.i); + this.h.b(packetdataserializer); ++ // Paper start - Anti-Xray - Add chunk packet info ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setBitsPerObject(chunkSectionIndex, this.getBitsPerObject()); ++ chunkPacketInfo.setDataPalette(chunkSectionIndex, this.getDataPalette()); ++ chunkPacketInfo.setDataBitsIndex(chunkSectionIndex, packetdataserializer.writerIndex() + PacketDataSerializer.countBytes(this.getDataBits().getDataBits().length)); ++ chunkPacketInfo.setPredefinedObjects(chunkSectionIndex, this.predefinedObjects); ++ } ++ // Paper end + packetdataserializer.a(this.a.a()); + this.b(); + } + + public synchronized void a(NBTTagList nbttaglist, long[] along) { // Paper - synchronize + this.a(); +- int i = Math.max(4, MathHelper.e(nbttaglist.size())); ++ // Paper - Anti-Xray - TODO: Should this.predefinedObjects.length just be added here (faster) or should the contents be compared to calculate the size (less RAM)? ++ int i = Math.max(4, MathHelper.e(nbttaglist.size() + (this.predefinedObjects == null ? 0 : this.predefinedObjects.length))); // Paper - Anti-Xray - Calculate the size with predefined objects + +- if (i != this.i) { ++ if (true || i != this.i) { // Paper - Anti-Xray - Not initialized yet + this.b(i); + } + + this.h.a(nbttaglist); ++ this.addPredefinedObjects(); // Paper - Anti-Xray - Add predefined objects + int j = along.length * 64 / 4096; + + if (this.h == this.b) { +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 9eeb99a21a6ed7f71ff64cf4cfdff646d31abbcf..9b308a10554b037ede0c455fbd3e906021218ddc 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -64,16 +64,24 @@ public class ProtoChunk implements IChunkAccess { + private long s; + private final Map t; + private volatile boolean u; ++ private final World world; // Paper - Anti-Xray - Add world + +- public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter) { ++ // Paper start - Anti-Xray - Add world ++ @Deprecated public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter) { this(chunkcoordintpair, chunkconverter, null); } // Notice for updates: Please make sure this constructor isn't used anywhere ++ public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, World world) { ++ // Paper end + this(chunkcoordintpair, chunkconverter, (ChunkSection[]) null, new ProtoChunkTickList<>((block) -> { + return block == null || block.getBlockData().isAir(); + }, chunkcoordintpair), new ProtoChunkTickList<>((fluidtype) -> { + return fluidtype == null || fluidtype == FluidTypes.EMPTY; +- }, chunkcoordintpair)); ++ }, chunkcoordintpair), world); // Paper - Anti-Xray - Add world + } + +- public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, @Nullable ChunkSection[] achunksection, ProtoChunkTickList protochunkticklist, ProtoChunkTickList protochunkticklist1) { ++ // Paper start - Anti-Xray - Add world ++ @Deprecated public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, @Nullable ChunkSection[] achunksection, ProtoChunkTickList protochunkticklist, ProtoChunkTickList protochunkticklist1) { this(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, null); } // Notice for updates: Please make sure this constructor isn't used anywhere ++ public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, @Nullable ChunkSection[] achunksection, ProtoChunkTickList protochunkticklist, ProtoChunkTickList protochunkticklist1, World world) { ++ this.world = world; ++ // Paper end + this.f = Maps.newEnumMap(HeightMap.Type.class); + this.g = ChunkStatus.EMPTY; + this.h = Maps.newHashMap(); +@@ -228,7 +236,7 @@ public class ProtoChunk implements IChunkAccess { + + public ChunkSection a(int i) { + if (this.j[i] == Chunk.a) { +- this.j[i] = new ChunkSection(i << 4); ++ this.j[i] = new ChunkSection(i << 4, this, this.world, true); // Paper - Anti-Xray - Add parameters + } + + return this.j[i]; +diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunkExtension.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunkExtension.java +index 9351e6ba541d440c485b6e4a3209170c5756e31e..7a82d43d51d80a3054e0871bf4b9aa7635920efc 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunkExtension.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunkExtension.java +@@ -27,7 +27,7 @@ public class ProtoChunkExtension extends ProtoChunk { + private final Chunk a; + + public ProtoChunkExtension(Chunk chunk) { +- super(chunk.getPos(), ChunkConverter.a); ++ super(chunk.getPos(), ChunkConverter.a, chunk.world); // Paper - Anti-Xray - Add parameter + this.a = chunk; + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +index 98bc26c7ae01884eb53766e72fc7cbabbf065e6e..c652897aae99c48c6cc020b5d64f6a8b02beecb5 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +@@ -101,7 +101,7 @@ public class ChunkRegionLoader { + byte b0 = nbttagcompound2.getByte("Y"); + + if (nbttagcompound2.hasKeyOfType("Palette", 9) && nbttagcompound2.hasKeyOfType("BlockStates", 12)) { +- ChunkSection chunksection = new ChunkSection(b0 << 4); ++ ChunkSection chunksection = new ChunkSection(b0 << 4, null, worldserver, false); // Paper - Anti-Xray - Add parameters + + chunksection.getBlocks().a(nbttagcompound2.getList("Palette", 10), nbttagcompound2.getLongArray("BlockStates")); + chunksection.recalcBlockCounts(); +@@ -165,7 +165,7 @@ public class ChunkRegionLoader { + // CraftBukkit end + }); + } else { +- ProtoChunk protochunk = new ProtoChunk(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1); ++ ProtoChunk protochunk = new ProtoChunk(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, worldserver); // Paper - Anti-Xray - Add parameter + + protochunk.a(biomestorage); + object = protochunk; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index a8e94f69faec93661dc6ae2efeec44b8bfd2e965..c36f55f178166eb099cc5c64784be5a9f4750199 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -44,7 +44,7 @@ public class CraftChunk implements Chunk { + private final WorldServer worldServer; + private final int x; + private final int z; +- private static final DataPaletteBlock emptyBlockIDs = new ChunkSection(0).getBlocks(); ++ private static final DataPaletteBlock emptyBlockIDs = new ChunkSection(0, null, null, true).getBlocks(); // Paper - Anti-Xray - Add parameters + private static final byte[] emptyLight = new byte[2048]; + + public CraftChunk(net.minecraft.world.level.chunk.Chunk chunk) { +@@ -288,7 +288,7 @@ public class CraftChunk implements Chunk { + NBTTagCompound data = new NBTTagCompound(); + cs[i].getBlocks().a(data, "Palette", "BlockStates"); + +- DataPaletteBlock blockids = new DataPaletteBlock<>(ChunkSection.GLOBAL_PALETTE, net.minecraft.world.level.block.Block.REGISTRY_ID, GameProfileSerializer::c, GameProfileSerializer::a, Blocks.AIR.getBlockData()); // TODO: snapshot whole ChunkSection ++ DataPaletteBlock blockids = new DataPaletteBlock<>(ChunkSection.GLOBAL_PALETTE, net.minecraft.world.level.block.Block.REGISTRY_ID, GameProfileSerializer::c, GameProfileSerializer::a, Blocks.AIR.getBlockData(), null, false); // TODO: snapshot whole ChunkSection // Paper - Anti-Xray - Add no predefined block data and don't initialize because it's done in the line below internally + blockids.a(data.getList("Palette", CraftMagicNumbers.NBT.TAG_COMPOUND), data.getLongArray("BlockStates")); + + sectionBlockIDs[i] = blockids; +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +index 3d15915275331cb767750c24c89b4b43d43033ef..afca0038bb74ac53f07a25729a3c1542e244c6fd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +@@ -21,9 +21,11 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + private final int maxHeight; + private final ChunkSection[] sections; + private Set tiles; ++ private World world; // Paper - Anti-Xray - Add world + + public CraftChunkData(World world) { + this(world.getMaxHeight()); ++ this.world = world; // Paper - Anti-Xray - Add world + } + + /* pp for tests */ CraftChunkData(int maxHeight) { +@@ -157,7 +159,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + private ChunkSection getChunkSection(int y, boolean create) { + ChunkSection section = sections[y >> 4]; + if (create && section == null) { +- sections[y >> 4] = section = new ChunkSection(y >> 4 << 4); ++ sections[y >> 4] = section = new ChunkSection(y >> 4 << 4, null, world instanceof org.bukkit.craftbukkit.CraftWorld ? ((org.bukkit.craftbukkit.CraftWorld) world).getHandle() : null, true); // Paper - Anti-Xray - Add parameters + } + return section; + } diff --git a/patches/server-unmapped/0001/0365-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch b/patches/server-unmapped/0001/0365-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch new file mode 100644 index 0000000000..528f3c9022 --- /dev/null +++ b/patches/server-unmapped/0001/0365-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch @@ -0,0 +1,57 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index c45493e88bf7e8811be2759ff9ac19e3fe9d938a..384cb363eed794551bee6b0ec11ba1be92a3d7ac 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -464,6 +464,16 @@ public class PaperWorldConfig { + maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24); + } + ++ public boolean countAllMobsForSpawning = false; ++ private void countAllMobsForSpawning() { ++ countAllMobsForSpawning = getBoolean("count-all-mobs-for-spawning", false); ++ if (countAllMobsForSpawning) { ++ log("Counting all mobs for spawning. Mob farms may reduce natural spawns elsewhere in world."); ++ } else { ++ log("Using improved mob spawn limits (Only Natural Spawns impact spawn limits for more natural spawns)"); ++ } ++ } ++ + public boolean antiXray; + public EngineMode engineMode; + public int maxChunkSectionIndex; +diff --git a/src/main/java/net/minecraft/world/level/SpawnerCreature.java b/src/main/java/net/minecraft/world/level/SpawnerCreature.java +index 5307488fa48ffa91446dd4457de1ce6a8f61da61..d30a3de84dc75a57680052904337af02b6b80636 100644 +--- a/src/main/java/net/minecraft/world/level/SpawnerCreature.java ++++ b/src/main/java/net/minecraft/world/level/SpawnerCreature.java +@@ -81,6 +81,13 @@ public final class SpawnerCreature { + EnumCreatureType enumcreaturetype = entity.getEntityType().e(); + + if (enumcreaturetype != EnumCreatureType.MISC) { ++ // Paper start - Only count natural spawns ++ if (!entity.world.paperConfig.countAllMobsForSpawning && ++ !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL || ++ entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) { ++ continue; ++ } ++ // Paper end + BlockPosition blockposition = entity.getChunkCoordinates(); + long j = ChunkCoordIntPair.pair(blockposition.getX() >> 4, blockposition.getZ() >> 4); + diff --git a/patches/server-unmapped/0001/0366-Configurable-projectile-relative-velocity.patch b/patches/server-unmapped/0001/0366-Configurable-projectile-relative-velocity.patch new file mode 100644 index 0000000000..55dc364cd3 --- /dev/null +++ b/patches/server-unmapped/0001/0366-Configurable-projectile-relative-velocity.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lucavon +Date: Tue, 23 Jul 2019 20:29:20 -0500 +Subject: [PATCH] Configurable projectile relative velocity + +This patch adds an option "disable relative projectile velocity", which, when +nabled, will cause projectiles to ignore the shooter's current velocity, +like they did in Minecraft 1.8 and prior. +If a player is falling, for example, their shooting range will be drastically +reduced, as a downwards velocity is applied to the projectile. This prevents +players from saving themselves from falling off floating islands, for example, +as a thrown ender pearl will not make it back to the island, while it would +have in 1.8. + +While this could easily be done with plugins, too, there are multiple problems: +P1) If multiple plugins cancel the velocity by subtracting the shooter's velocity +from the projectile's velocity, the projectile's velocity would be different. +As there's no way to detect whether the projectile's velocity has already been +adjusted to ignore the player's velocity, plugins can't not do it if it's not +necessary. +P2) I've noticed some inconsistencies, e.g. weird velocity when shooting while +using an elytra. Checking for those inconsistencies is possible, but not as +efficient as just not applying the velocity in the first place. +P3) Solutions for 1) and especially 2) might not be future-proof, while this +server-internal fix makes this change future-proof. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 384cb363eed794551bee6b0ec11ba1be92a3d7ac..1ee2cced100626e48eb36ee14f84b9257c79a2f8 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -507,4 +507,9 @@ public class PaperWorldConfig { + Bukkit.getLogger().warning("You have enabled permission-based Anti-Xray checking - depending on your permission plugin, this may cause performance issues"); + } + } ++ ++ public boolean disableRelativeProjectileVelocity; ++ private void disableRelativeProjectileVelocity() { ++ disableRelativeProjectileVelocity = getBoolean("game-mechanics.disable-relative-projectile-velocity", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java +index a33f3924a95b86c2337c455f30de9bb257cb8db4..37b1febb45b900dfe4b225152e66bc4be83df220 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java +@@ -126,7 +126,7 @@ public abstract class IProjectile extends Entity { + this.shoot((double) f5, (double) f6, (double) f7, f3, f4); + Vec3D vec3d = entity.getMot(); + +- this.setMot(this.getMot().add(vec3d.x, entity.isOnGround() ? 0.0D : vec3d.y, vec3d.z)); ++ if (!entity.world.paperConfig.disableRelativeProjectileVelocity) this.setMot(this.getMot().add(vec3d.x, entity.isOnGround() ? 0.0D : vec3d.y, vec3d.z)); // Paper - allow disabling relative velocity + } + + protected void a(MovingObjectPosition movingobjectposition) { diff --git a/patches/server-unmapped/0001/0367-Mark-entities-as-being-ticked-when-notifying-navigat.patch b/patches/server-unmapped/0001/0367-Mark-entities-as-being-ticked-when-notifying-navigat.patch new file mode 100644 index 0000000000..6e29d9d66f --- /dev/null +++ b/patches/server-unmapped/0001/0367-Mark-entities-as-being-ticked-when-notifying-navigat.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 28 Jul 2019 00:51:11 +0100 +Subject: [PATCH] Mark entities as being ticked when notifying navigation + + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index caf3d4df460d2d6dad6e68a68e1256e3603e3891..1b05d2394244d85a63ecd8336f7dd1d05f4fdffe 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1476,6 +1476,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + VoxelShape voxelshape1 = iblockdata1.getCollisionShape(this, blockposition); + + if (VoxelShapes.c(voxelshape, voxelshape1, OperatorBoolean.NOT_SAME)) { ++ boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper + Iterator iterator = this.navigators.iterator(); + + while (iterator.hasNext()) { +@@ -1497,6 +1498,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + } + ++ this.tickingEntities = wasTicking; // Paper + } + } + diff --git a/patches/server-unmapped/0001/0368-offset-item-frame-ticking.patch b/patches/server-unmapped/0001/0368-offset-item-frame-ticking.patch new file mode 100644 index 0000000000..b2190b5c55 --- /dev/null +++ b/patches/server-unmapped/0001/0368-offset-item-frame-ticking.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Tue, 30 Jul 2019 03:17:16 +0500 +Subject: [PATCH] offset item frame ticking + + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityHanging.java b/src/main/java/net/minecraft/world/entity/decoration/EntityHanging.java +index 477c86bec21159608707c1b3bf2ad5f2b455214f..9d491240bcb3ba6ffbee963a13d31aa7b6cd5d45 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/EntityHanging.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityHanging.java +@@ -37,7 +37,7 @@ public abstract class EntityHanging extends Entity { + protected static final Predicate b = (entity) -> { + return entity instanceof EntityHanging; + }; +- private int e; ++ private int e; { this.e = this.getId() % this.world.spigotConfig.hangingTickFrequency; } // Paper + public BlockPosition blockPosition; + protected EnumDirection direction; + diff --git a/patches/server-unmapped/0001/0369-Avoid-hopper-searches-if-there-are-no-items.patch b/patches/server-unmapped/0001/0369-Avoid-hopper-searches-if-there-are-no-items.patch new file mode 100644 index 0000000000..77f09da2e9 --- /dev/null +++ b/patches/server-unmapped/0001/0369-Avoid-hopper-searches-if-there-are-no-items.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: CullanP +Date: Thu, 3 Mar 2016 02:13:38 -0600 +Subject: [PATCH] Avoid hopper searches if there are no items + +Hoppers searching for items and minecarts is the most expensive part of hopper ticking. +We keep track of the number of minecarts and items in a chunk. +If there are no items in the chunk, we skip searching for items. +If there are no minecarts in the chunk, we skip searching for them. + +Usually hoppers aren't near items, so we can skip most item searches. +And since minecart hoppers are used _very_ rarely near we can avoid alot of searching there. + +Combined, this adds up a lot. + +diff --git a/src/main/java/net/minecraft/world/entity/IEntitySelector.java b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +index cb5cda5e6497edeb801ef712f9bd8823cb055750..1a6f8aec32af85717f5d56e0b00a02cda88ce028 100644 +--- a/src/main/java/net/minecraft/world/entity/IEntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +@@ -16,6 +16,7 @@ public final class IEntitySelector { + public static final Predicate c = (entity) -> { + return entity.isAlive() && !entity.isVehicle() && !entity.isPassenger(); + }; ++ public static final Predicate isInventory() { return d; } // Paper - OBFHELPER + public static final Predicate d = (entity) -> { + return entity instanceof IInventory && entity.isAlive(); + }; +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index 3fdce0e6fa34eb4b1eafc618068a3fb06abd5ec1..e7bb33125a25b9e5a68013b15d7b5b6b6769ab9b 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -32,10 +32,13 @@ import net.minecraft.server.level.PlayerChunk; + import net.minecraft.server.level.WorldServer; + import net.minecraft.util.EntitySlice; + import net.minecraft.util.MathHelper; ++import net.minecraft.world.IInventory; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.IEntitySelector; + import net.minecraft.world.entity.boss.EntityComplexPart; + import net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon; ++import net.minecraft.world.entity.item.EntityItem; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.GeneratorAccess; + import net.minecraft.world.level.TickList; +@@ -123,6 +126,10 @@ public class Chunk implements IChunkAccess { + return removed; + } + } ++ // Track the number of minecarts and items ++ // Keep this synced with entitySlices.add() and entitySlices.remove() ++ private final int[] itemCounts = new int[16]; ++ private final int[] inventoryEntityCounts = new int[16]; + // Paper end + + public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage, ChunkConverter chunkconverter, TickList ticklist, TickList ticklist1, long i, @Nullable ChunkSection[] achunksection, @Nullable Consumer consumer) { +@@ -582,6 +589,13 @@ public class Chunk implements IChunkAccess { + entity.chunkZ = this.loc.z; + this.entities.add(entity); // Paper - per chunk entity list + this.entitySlices[k].add(entity); ++ // Paper start ++ if (entity instanceof EntityItem) { ++ itemCounts[k]++; ++ } else if (entity instanceof IInventory) { ++ inventoryEntityCounts[k]++; ++ } ++ // Paper end + entity.entitySlice = this.entitySlices[k]; // Paper + this.markDirty(); // Paper + } +@@ -615,6 +629,11 @@ public class Chunk implements IChunkAccess { + if (!this.entitySlices[i].remove(entity)) { + return; + } ++ if (entity instanceof EntityItem) { ++ itemCounts[i]--; ++ } else if (entity instanceof IInventory) { ++ inventoryEntityCounts[i]--; ++ } + entityCounts.decrement(entity.getMinecraftKeyString()); + this.markDirty(); // Paper + // Paper end +@@ -900,6 +919,14 @@ public class Chunk implements IChunkAccess { + for (int k = i; k <= j; ++k) { + Iterator iterator = this.entitySlices[k].iterator(); // Spigot + ++ // Paper start - Don't search for inventories if we have none, and that is all we want ++ /* ++ * We check if they want inventories by seeing if it is the static `IEntitySelector.d` ++ * ++ * Make sure the inventory selector stays in sync. ++ * It should be the one that checks `var1 instanceof IInventory && var1.isAlive()` ++ */ ++ if (predicate == IEntitySelector.isInventory() && inventoryEntityCounts[k] <= 0) continue; + while (iterator.hasNext()) { + T entity = (T) iterator.next(); // CraftBukkit - decompile error + if (entity.shouldBeRemoved) continue; // Paper +@@ -920,9 +947,29 @@ public class Chunk implements IChunkAccess { + i = MathHelper.clamp(i, 0, this.entitySlices.length - 1); + j = MathHelper.clamp(j, 0, this.entitySlices.length - 1); + ++ // Paper start ++ int[] counts; ++ if (EntityItem.class.isAssignableFrom(oclass)) { ++ counts = itemCounts; ++ } else if (IInventory.class.isAssignableFrom(oclass)) { ++ counts = inventoryEntityCounts; ++ } else { ++ counts = null; ++ } ++ // Paper end + for (int k = i; k <= j; ++k) { ++ if (counts != null && counts[k] <= 0) continue; // Paper - Don't check a chunk if it doesn't have the type we are looking for + Iterator iterator = this.entitySlices[k].iterator(); // Spigot + ++ // Paper start - Don't search for inventories if we have none, and that is all we want ++ /* ++ * We check if they want inventories by seeing if it is the static `IEntitySelector.d` ++ * ++ * Make sure the inventory selector stays in sync. ++ * It should be the one that checks `var1 instanceof IInventory && var1.isAlive()` ++ */ ++ if (predicate == IEntitySelector.isInventory() && inventoryEntityCounts[k] <= 0) continue; ++ // Paper end + while (iterator.hasNext()) { + T t0 = (T) iterator.next(); // CraftBukkit - decompile error + if (t0.shouldBeRemoved) continue; // Paper diff --git a/patches/server-unmapped/0001/0370-Asynchronous-chunk-IO-and-loading.patch b/patches/server-unmapped/0001/0370-Asynchronous-chunk-IO-and-loading.patch new file mode 100644 index 0000000000..567e2cc477 --- /dev/null +++ b/patches/server-unmapped/0001/0370-Asynchronous-chunk-IO-and-loading.patch @@ -0,0 +1,4203 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 13 Jul 2019 09:23:10 -0700 +Subject: [PATCH] Asynchronous chunk IO and loading + +This patch re-adds a file IO thread as well as shoving de-serializing +chunk NBT data onto worker threads. This patch also will shove +chunk data serialization onto the same worker threads when the chunk +is unloaded - this cannot be done for regular saves since that's unsafe. + +The file IO Thread + +Unlike 1.13 and below, the file IO thread is prioritized - IO tasks can +be reoredered, however they are "stuck" to a world & coordinate. + +Scheduling IO tasks works as follows, given a world & coordinate - location: + +The IO thread has been designed to ensure that reads and writes appear to +occur synchronously for a given location, however the implementation also +has the unfortunate side-effect of making every write appear as if +they occur without failure. + +The IO thread has also been designed to accomodate Mojang's decision to +store chunk data and POI data separately. It can independently schedule +tasks for each. + +However threads can wait for writes to complete and check if: + - The write was overwriten by another scheduler + - The write failed (however it does not indicate whether it was overwritten by another scheduler) + +Scheduling reads: + + - If a write task is in progress, the task is not scheduled and returns the in-progress write data + This means that readers cannot modify the NBTTagCompound returned and must clone if it they wish to write + - If a write task is not in progress but a read task is in progress, then the read task is simply chained + This means that again, readers cannot modify the NBTTagCompound returned + +Scheduling writes: + + - If a read task is in progress, ignore the read task and schedule the write + We cannot complete the read task since we assume it wants old data - not current + - If a write task is pending, overwrite the write data + The file IO thread does correctly handle cases where the data is overwritten when it + is writing data (before completing a task it will check if the data was overwritten and + will retry). + +When the file IO thread executes a task for a location, the it will +execute the read task first (if it exists), then it will execute the +write task. This ensures that, even when scheduling at different +priorities, that reads/writes for a location act synchronously. + +The downside of the file IO thread is that write failure can only be +indicated to the scheduling thread if: + +- No other thread decides to schedule another write for the location +concurrently +- The scheduling thread blocks on the write to complete (however the +current implementation can be modified to indicate success +asynchronously) + +The file io thread can be modified easily to provide indications +of write failure and write overwriting if needed. + +The upside of the file IO thread is that if a write failures, then +chunk data is not lost until server restart. This leaves more room +for spurious failure. + +Finally, the io thread will indicate to the console when reads +or writes fail - with relevant detail. + +Asynchronous chunk data serialization for unloading chunks + +When chunks unload they make a call to PlayerChunkMap#saveChunk(IChunkAccess). +Even if I make the IO asynchronous for this call, the data serialization +still hits pretty hard. And given that now the chunk system will +aggressively unload chunks more often (queued immediately at +ticket level 45 or higher), unloads occur more often, and +combined with our changes to the unload queue to make it +significantly more aggresive - chunk unloads can hit pretty hard. +Especially players running around with elytras and fireworks. + +For serializing chunk data off main, there are some tasks which cannot be +done asynchronously. Lighting data must be saved beforehand as well as +potentially some tick lists. These are completed before scheduling the +asynchronous save. + +However serializing chunk data off of the main thread is still risky. +Even though this patch schedules the save to occur after ALL references +of the chunk are removed from the world, plugins can still technically +access entities inside the chunks. For this, if the serialization task +fails for any reason, it will be re-scheduled to be serialized on the +main thread - with the hopes that the reason it failed was due to a plugin +and not an error with the save code itself. Like vanilla code - if the +serialization fails, the chunk data is lost. + +Asynchronous chunk io/loading + +Mojang's current implementation for loading chunk data off disk is +to return a CompletableFuture that will be completed by scheduling a +task to be executed on the world's chunk queue (which is only drained +on the main thread). This task will read the IO off disk and it will +apply data conversions & deserialization synchronously. Obviously +all 3 of these operations are expensive however all can be completed +asynchronously instead. + +The solution this patch uses is as follows: + +0. If an asynchronous chunk save is in progress (see above), wait +for that task to complete. It will use the serialized NBTTagCompound +created by the task. If the task fails to complete, then we would continue +with step 1. If it does not, we skip step 1. (Note: We actually load +POI data no matter what in this case). +1. Schedule an IO task to read chunk & poi data off disk. +2. The IO task will schedule a chunk load task. +3. The chunk load task executes on the async chunk loader threads +and will apply datafixers & de-serialize the chunk into a ProtoChunk +or ProtoChunkExtension. +4. The in progress chunk is then passed on to the world's chunk queue +to complete the ComletableFuture and execute any of the synchronous +tasks required to be executed by the chunk load task (i.e lighting +and some poi tasks). + +diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +index fa154ed68187a2020e814db6345a8cc1119ab4ba..2da28784ee427001b1137c859f0b4c350abd3110 100644 +--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java ++++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +@@ -58,6 +58,17 @@ 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 chunkSaveOverwriteCheck; ++ public final Timing chunkSaveDataSerialization; ++ public final Timing chunkSaveIOWait; ++ public final Timing chunkUnloadPrepareSave; ++ public final Timing chunkUnloadPOISerialization; ++ public final Timing chunkUnloadDataSave; ++ + public WorldTimingsHandler(World server) { + String name = ((WorldDataServer) server.getWorldData()).getName() + " - "; + +@@ -111,6 +122,17 @@ 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"); ++ chunkSaveOverwriteCheck = Timings.ofSafe(name + "Chunk save - Chunk Overwrite Check"); ++ 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(WorldServer worldserver, String timingsType) { +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index a7a02072e5c7ce62cbecbb638fcc74abf2fb57ee..f657e9b6bb3d24a6c77ef584711a003d1eea0341 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -1,5 +1,6 @@ + package com.destroystokyo.paper; + ++import com.destroystokyo.paper.io.chunk.ChunkTaskManager; + import com.google.common.base.Functions; + import com.google.common.base.Joiner; + import com.google.common.collect.ImmutableSet; +@@ -43,7 +44,7 @@ import java.util.stream.Collectors; + + public class PaperCommand extends Command { + private static final String BASE_PERM = "bukkit.command.paper."; +- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build(); ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting").build(); + + public PaperCommand(String name) { + super(name); +@@ -155,6 +156,9 @@ public class PaperCommand extends Command { + case "debug": + doDebug(sender, args); + break; ++ case "dumpwaiting": ++ ChunkTaskManager.dumpAllChunkLoadInfo(); ++ break; + case "chunkinfo": + doChunkInfo(sender, args); + break; +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 469f78775b03cf363d88e35c69c0dc185c22547c..8bf4d2b8c38c02d6a5b2fea37113689a252f1571 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -1,5 +1,6 @@ + package com.destroystokyo.paper; + ++import com.destroystokyo.paper.io.chunk.ChunkTaskManager; + import com.google.common.base.Strings; + import com.google.common.base.Throwables; + +@@ -352,4 +353,54 @@ public class PaperConfig { + maxBookPageSize = getInt("settings.book-size.page-max", maxBookPageSize); + maxBookTotalSizeMultiplier = getDouble("settings.book-size.total-multiplier", maxBookTotalSizeMultiplier); + } ++ ++ public static boolean asyncChunks = false; ++ private static void asyncChunks() { ++ ConfigurationSection section; ++ if (version < 15) { ++ section = config.createSection("settings.async-chunks"); ++ section.set("threads", -1); ++ } else { ++ section = config.getConfigurationSection("settings.async-chunks"); ++ if (section == null) { ++ section = config.createSection("settings.async-chunks"); ++ } ++ } ++ // Clean up old configs ++ if (section.contains("load-threads")) { ++ if (!section.contains("threads")) { ++ section.set("threads", section.get("load-threads")); ++ } ++ section.set("load-threads", null); ++ } ++ section.set("generation", null); ++ section.set("enabled", null); ++ section.set("thread-per-world-generation", null); ++ ++ int threads = getInt("settings.async-chunks.threads", -1); ++ int cpus = Runtime.getRuntime().availableProcessors(); ++ if (threads <= 0) { ++ threads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Math.max(1, cpus - 1)); ++ } ++ if (cpus == 1 && !Boolean.getBoolean("Paper.allowAsyncChunksSingleCore")) { ++ asyncChunks = false; ++ } else { ++ asyncChunks = true; ++ } ++ ++ // Let Shared Host set some limits ++ String sharedHostThreads = System.getenv("PAPER_ASYNC_CHUNKS_SHARED_HOST_THREADS"); ++ if (sharedHostThreads != null) { ++ try { ++ threads = Math.max(1, Math.min(threads, Integer.parseInt(sharedHostThreads))); ++ } catch (NumberFormatException ignored) {} ++ } ++ ++ if (!asyncChunks) { ++ log("Async Chunks: Disabled - Chunks will be managed synchronously, and will cause tremendous lag."); ++ } else { ++ ChunkTaskManager.initGlobalLoadThreads(threads); ++ log("Async Chunks: Enabled - Chunks will be loaded much faster, without lag."); ++ } ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/io/IOUtil.java b/src/main/java/com/destroystokyo/paper/io/IOUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5af0ac3d9e87c06053e65433060f15779c156c2a +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/IOUtil.java +@@ -0,0 +1,62 @@ ++package com.destroystokyo.paper.io; ++ ++import org.bukkit.Bukkit; ++ ++public final class IOUtil { ++ ++ /* Copied from concrete or concurrentutil */ ++ ++ public static long getCoordinateKey(final int x, final int z) { ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ } ++ ++ public static int getCoordinateX(final long key) { ++ return (int)key; ++ } ++ ++ public static int getCoordinateZ(final long key) { ++ return (int)(key >>> 32); ++ } ++ ++ public static int getRegionCoordinate(final int chunkCoordinate) { ++ return chunkCoordinate >> 5; ++ } ++ ++ public static int getChunkInRegion(final int chunkCoordinate) { ++ return chunkCoordinate & 31; ++ } ++ ++ public static String genericToString(final Object object) { ++ return object == null ? "null" : object.getClass().getName() + ":" + object.toString(); ++ } ++ ++ public static T notNull(final T obj) { ++ if (obj == null) { ++ throw new NullPointerException(); ++ } ++ return obj; ++ } ++ ++ public static T notNull(final T obj, final String msgIfNull) { ++ if (obj == null) { ++ throw new NullPointerException(msgIfNull); ++ } ++ return obj; ++ } ++ ++ public static void arrayBounds(final int off, final int len, final int arrayLength, final String msgPrefix) { ++ if (off < 0 || len < 0 || (arrayLength - off) < len) { ++ throw new ArrayIndexOutOfBoundsException(msgPrefix + ": off: " + off + ", len: " + len + ", array length: " + arrayLength); ++ } ++ } ++ ++ public static int getPriorityForCurrentThread() { ++ return Bukkit.isPrimaryThread() ? PrioritizedTaskQueue.HIGHEST_PRIORITY : PrioritizedTaskQueue.NORMAL_PRIORITY; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static void rethrow(final Throwable throwable) throws T { ++ throw (T)throwable; ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java b/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9fe91f9512ee8c2589fc8da76bda5f6d70c9fac4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java +@@ -0,0 +1,606 @@ ++package com.destroystokyo.paper.io; ++ ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.chunk.storage.RegionFile; ++import org.apache.logging.log4j.Logger; ++ ++import java.io.IOException; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.function.Consumer; ++import java.util.function.Function; ++ ++/** ++ * Prioritized singleton thread responsible for all chunk IO that occurs in a minecraft server. ++ * ++ *

++ * Singleton access: {@link Holder#INSTANCE} ++ *

++ * ++ *

++ * All functions provided are MT-Safe, however certain ordering constraints are (but not enforced): ++ *

  • ++ * Chunk saves may not occur for unloaded chunks. ++ *
  • ++ *
  • ++ * Tasks must be scheduled on the main thread. ++ *
  • ++ *

    ++ * ++ * @see Holder#INSTANCE ++ * @see #scheduleSave(WorldServer, int, int, NBTTagCompound, NBTTagCompound, int) ++ * @see #loadChunkDataAsync(WorldServer, int, int, int, Consumer, boolean, boolean, boolean) ++ */ ++public final class PaperFileIOThread extends QueueExecutorThread { ++ ++ public static final Logger LOGGER = MinecraftServer.LOGGER; ++ public static final NBTTagCompound FAILURE_VALUE = new NBTTagCompound(); ++ ++ public static final class Holder { ++ ++ public static final PaperFileIOThread INSTANCE = new PaperFileIOThread(); ++ ++ static { ++ INSTANCE.start(); ++ } ++ } ++ ++ private final AtomicLong writeCounter = new AtomicLong(); ++ ++ private PaperFileIOThread() { ++ super(new PrioritizedTaskQueue<>(), (int)(1.0e6)); // 1.0ms spinwait time ++ this.setName("Paper RegionFile IO Thread"); ++ this.setPriority(Thread.NORM_PRIORITY - 1); // we keep priority close to normal because threads can wait on us ++ this.setUncaughtExceptionHandler((final Thread unused, final Throwable thr) -> { ++ LOGGER.fatal("Uncaught exception thrown from IO thread, report this!", thr); ++ }); ++ } ++ ++ /* run() is implemented by superclass */ ++ ++ /* ++ * ++ * IO thread will perform reads before writes ++ * ++ * How reads/writes are scheduled: ++ * ++ * If read in progress while scheduling write, ignore read and schedule write ++ * If read in progress while scheduling read (no write in progress), chain the read task ++ * ++ * ++ * If write in progress while scheduling read, use the pending write data and ret immediately ++ * If write in progress 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. When writes fail the data is kept so future reads will actually ++ * read the failed write data. This should hopefully act as a way to prevent data loss for spurious fails for writing data. ++ * ++ */ ++ ++ /** ++ * Attempts to bump the priority of all IO tasks for the given chunk coordinates. This has no effect if no tasks are queued. ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param priority Priority level to try to bump to ++ */ ++ public void bumpPriority(final WorldServer world, final int chunkX, final int chunkZ, final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority: " + priority); ++ } ++ ++ final Long key = Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)); ++ ++ final ChunkDataTask poiTask = world.poiDataController.tasks.get(key); ++ final ChunkDataTask chunkTask = world.chunkDataController.tasks.get(key); ++ ++ if (poiTask != null) { ++ poiTask.raisePriority(priority); ++ } ++ if (chunkTask != null) { ++ chunkTask.raisePriority(priority); ++ } ++ } ++ ++ public NBTTagCompound getPendingWrite(final WorldServer world, final int chunkX, final int chunkZ, final boolean poiData) { ++ final ChunkDataController taskController = poiData ? world.poiDataController : world.chunkDataController; ++ ++ final ChunkDataTask dataTask = taskController.tasks.get(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ))); ++ ++ if (dataTask == null) { ++ return null; ++ } ++ ++ final ChunkDataController.InProgressWrite write = dataTask.inProgressWrite; ++ ++ if (write == null) { ++ return null; ++ } ++ ++ return write.data; ++ } ++ ++ /** ++ * Sets the priority of all IO tasks for the given chunk coordinates. This has no effect if no tasks are queued. ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param priority Priority level to set to ++ */ ++ public void setPriority(final WorldServer world, final int chunkX, final int chunkZ, final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority: " + priority); ++ } ++ ++ final Long key = Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)); ++ ++ final ChunkDataTask poiTask = world.poiDataController.tasks.get(key); ++ final ChunkDataTask chunkTask = world.chunkDataController.tasks.get(key); ++ ++ if (poiTask != null) { ++ poiTask.updatePriority(priority); ++ } ++ if (chunkTask != null) { ++ chunkTask.updatePriority(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 poiData Chunk point of interest data. If {@code null}, then no poi data is saved. ++ * @param chunkData Chunk data. If {@code null}, then no chunk data is saved. ++ * @param priority Priority level for this task. See {@link PrioritizedTaskQueue} ++ * @throws IllegalArgumentException If both {@code poiData} and {@code chunkData} are {@code null}. ++ * @throws IllegalStateException If the file io thread has shutdown. ++ */ ++ public void scheduleSave(final WorldServer world, final int chunkX, final int chunkZ, ++ final NBTTagCompound poiData, final NBTTagCompound chunkData, ++ final int priority) throws IllegalArgumentException { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority: " + priority); ++ } ++ ++ final long writeCounter = this.writeCounter.getAndIncrement(); ++ ++ if (poiData != null) { ++ this.scheduleWrite(world.poiDataController, world, chunkX, chunkZ, poiData, priority, writeCounter); ++ } ++ if (chunkData != null) { ++ this.scheduleWrite(world.chunkDataController, world, chunkX, chunkZ, chunkData, priority, writeCounter); ++ } ++ } ++ ++ private void scheduleWrite(final ChunkDataController dataController, final WorldServer world, ++ final int chunkX, final int chunkZ, final NBTTagCompound data, final int priority, final long writeCounter) { ++ dataController.tasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkDataTask taskRunning) -> { ++ if (taskRunning == null) { ++ // no task is scheduled ++ ++ // create task ++ final ChunkDataTask newTask = new ChunkDataTask(priority, world, chunkX, chunkZ, dataController); ++ newTask.inProgressWrite = new ChunkDataController.InProgressWrite(); ++ newTask.inProgressWrite.writeCounter = writeCounter; ++ newTask.inProgressWrite.data = data; ++ ++ PaperFileIOThread.this.queueTask(newTask); // schedule ++ return newTask; ++ } ++ ++ taskRunning.raisePriority(priority); ++ ++ if (taskRunning.inProgressWrite == null) { ++ taskRunning.inProgressWrite = new ChunkDataController.InProgressWrite(); ++ } ++ ++ boolean reschedule = taskRunning.inProgressWrite.writeCounter == -1L; ++ ++ // synchronize for readers ++ //noinspection SynchronizationOnLocalVariableOrMethodParameter ++ synchronized (taskRunning) { ++ taskRunning.inProgressWrite.data = data; ++ taskRunning.inProgressWrite.writeCounter = writeCounter; ++ } ++ ++ if (reschedule) { ++ // We need to reschedule this task since the previous one is not currently scheduled since it failed ++ taskRunning.reschedule(priority); ++ } ++ ++ return taskRunning; ++ }); ++ } ++ ++ /** ++ * Same as {@link #loadChunkDataAsync(WorldServer, int, int, int, Consumer, boolean, boolean, boolean)}, except this function returns ++ * a {@link CompletableFuture} which is potentially completed ASYNCHRONOUSLY ON THE FILE IO THREAD when the load task ++ * has completed. ++ *

    ++ * Note that if the chunk fails to load the returned future is completed with {@code null}. ++ *

    ++ */ ++ public CompletableFuture loadChunkDataAsyncFuture(final WorldServer world, final int chunkX, final int chunkZ, ++ final int priority, final boolean readPoiData, final boolean readChunkData, ++ final boolean intendingToBlock) { ++ final CompletableFuture future = new CompletableFuture<>(); ++ this.loadChunkDataAsync(world, chunkX, chunkZ, priority, future::complete, readPoiData, readChunkData, intendingToBlock); ++ return future; ++ } ++ ++ /** ++ * Schedules a load to be executed asynchronously. ++ *

    ++ * Impl notes: ++ *

    ++ *
  • ++ * If a chunk fails to load, the {@code onComplete} parameter is completed with {@code null}. ++ *
  • ++ *
  • ++ * It is possible for the {@code onComplete} parameter to be given {@link ChunkData} containing data ++ * this call did not request. ++ *
  • ++ *
  • ++ * 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 priority Priority level for this task. See {@link PrioritizedTaskQueue} ++ * @param onComplete Consumer to execute once this task has completed ++ * @param readPoiData Whether to read point of interest data. If {@code false}, the {@code NBTTagCompound} will be {@code null}. ++ * @param readChunkData Whether to read chunk data. If {@code false}, the {@code NBTTagCompound} will be {@code null}. ++ * @return The {@link PrioritizedTaskQueue.PrioritizedTask} associated with this task. Note that this task does not support ++ * cancellation. ++ */ ++ public void loadChunkDataAsync(final WorldServer world, final int chunkX, final int chunkZ, ++ final int priority, final Consumer onComplete, ++ final boolean readPoiData, final boolean readChunkData, ++ final boolean intendingToBlock) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority: " + priority); ++ } ++ ++ if (!(readPoiData | readChunkData)) { ++ throw new IllegalArgumentException("Must read chunk data or poi data"); ++ } ++ ++ final ChunkData complete = new ChunkData(); ++ final boolean[] requireCompletion = new boolean[] { readPoiData, readChunkData }; ++ ++ if (readPoiData) { ++ this.scheduleRead(world.poiDataController, world, chunkX, chunkZ, (final NBTTagCompound poiData) -> { ++ complete.poiData = poiData; ++ ++ final boolean finished; ++ ++ // avoid a race condition where the file io thread completes and we complete synchronously ++ // Note: Synchronization can be elided if both of the accesses are volatile ++ synchronized (requireCompletion) { ++ requireCompletion[0] = false; // 0 -> poi data ++ finished = !requireCompletion[1]; // 1 -> chunk data ++ } ++ ++ if (finished) { ++ onComplete.accept(complete); ++ } ++ }, priority, intendingToBlock); ++ } ++ ++ if (readChunkData) { ++ this.scheduleRead(world.chunkDataController, world, chunkX, chunkZ, (final NBTTagCompound chunkData) -> { ++ complete.chunkData = chunkData; ++ ++ final boolean finished; ++ ++ // avoid a race condition where the file io thread completes and we complete synchronously ++ // Note: Synchronization can be elided if both of the accesses are volatile ++ synchronized (requireCompletion) { ++ requireCompletion[1] = false; // 1 -> chunk data ++ finished = !requireCompletion[0]; // 0 -> poi data ++ } ++ ++ if (finished) { ++ onComplete.accept(complete); ++ } ++ }, priority, intendingToBlock); ++ } ++ ++ } ++ ++ // Note: the onComplete may be called asynchronously or synchronously here. ++ private void scheduleRead(final ChunkDataController dataController, final WorldServer world, ++ final int chunkX, final int chunkZ, final Consumer onComplete, final int priority, ++ final boolean intendingToBlock) { ++ ++ Function tryLoadFunction = (final RegionFile file) -> { ++ if (file == null) { ++ return Boolean.TRUE; ++ } ++ return Boolean.valueOf(file.chunkExists(new ChunkCoordIntPair(chunkX, chunkZ))); ++ }; ++ ++ dataController.tasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkDataTask running) -> { ++ if (running == null) { ++ // not scheduled ++ ++ final Boolean shouldSchedule = intendingToBlock ? dataController.computeForRegionFile(chunkX, chunkZ, tryLoadFunction) : ++ dataController.computeForRegionFileIfLoaded(chunkX, chunkZ, tryLoadFunction); ++ ++ if (shouldSchedule == Boolean.FALSE) { ++ // not on disk ++ onComplete.accept(null); ++ return null; ++ } ++ ++ // set up task ++ final ChunkDataTask newTask = new ChunkDataTask(priority, world, chunkX, chunkZ, dataController); ++ newTask.inProgressRead = new ChunkDataController.InProgressRead(); ++ newTask.inProgressRead.readFuture.thenAccept(onComplete); ++ ++ PaperFileIOThread.this.queueTask(newTask); // schedule task ++ return newTask; ++ } ++ ++ running.raisePriority(priority); ++ ++ if (running.inProgressWrite == null) { ++ // chain to the read future ++ running.inProgressRead.readFuture.thenAccept(onComplete); ++ return running; ++ } ++ ++ // at this stage we have to use the in progress write's data to avoid an order issue ++ // we don't synchronize since all writes to data occur in the compute() call ++ onComplete.accept(running.inProgressWrite.data); ++ return running; ++ }); ++ } ++ ++ /** ++ * Same as {@link #loadChunkDataAsync(WorldServer, int, int, int, Consumer, boolean, boolean, boolean)}, except this function returns ++ * the {@link ChunkData} associated with the specified chunk when the task is complete. ++ * @return The chunk data, or {@code null} if the chunk failed to load. ++ */ ++ public ChunkData loadChunkData(final WorldServer world, final int chunkX, final int chunkZ, final int priority, ++ final boolean readPoiData, final boolean readChunkData) { ++ return this.loadChunkDataAsyncFuture(world, chunkX, chunkZ, priority, readPoiData, readChunkData, true).join(); ++ } ++ ++ /** ++ * Schedules the given task at the specified priority to be executed on the IO thread. ++ *

    ++ * Internal api. Do not use. ++ *

    ++ */ ++ public void runTask(final int priority, final Runnable runnable) { ++ this.queueTask(new GeneralTask(priority, runnable)); ++ } ++ ++ static final class GeneralTask extends PrioritizedTaskQueue.PrioritizedTask implements Runnable { ++ ++ private final Runnable run; ++ ++ public GeneralTask(final int priority, final Runnable run) { ++ super(priority); ++ this.run = IOUtil.notNull(run, "Task may not be null"); ++ } ++ ++ @Override ++ public void run() { ++ try { ++ this.run.run(); ++ } catch (final Throwable throwable) { ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ LOGGER.fatal("Failed to execute general task on IO thread " + IOUtil.genericToString(this.run), throwable); ++ } ++ } ++ } ++ ++ public static final class ChunkData { ++ ++ public NBTTagCompound poiData; ++ public NBTTagCompound chunkData; ++ ++ public ChunkData() {} ++ ++ public ChunkData(final NBTTagCompound poiData, final NBTTagCompound chunkData) { ++ this.poiData = poiData; ++ this.chunkData = chunkData; ++ } ++ } ++ ++ public static abstract class ChunkDataController { ++ ++ // ConcurrentHashMap synchronizes per chain, so reduce the chance of task's hashes colliding. ++ public final ConcurrentHashMap tasks = new ConcurrentHashMap<>(64, 0.5f); ++ ++ public abstract void writeData(final int x, final int z, final NBTTagCompound compound) throws IOException; ++ public abstract NBTTagCompound readData(final int x, final int z) throws IOException; ++ ++ public abstract T computeForRegionFile(final int chunkX, final int chunkZ, final Function function); ++ public abstract T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function function); ++ ++ public static final class InProgressWrite { ++ public long writeCounter; ++ public NBTTagCompound data; ++ } ++ ++ public static final class InProgressRead { ++ public final CompletableFuture readFuture = new CompletableFuture<>(); ++ } ++ } ++ ++ public static final class ChunkDataTask extends PrioritizedTaskQueue.PrioritizedTask implements Runnable { ++ ++ public ChunkDataController.InProgressWrite inProgressWrite; ++ public ChunkDataController.InProgressRead inProgressRead; ++ ++ private final WorldServer world; ++ private final int x; ++ private final int z; ++ private final ChunkDataController taskController; ++ ++ public ChunkDataTask(final int priority, final WorldServer world, final int x, final int z, final ChunkDataController taskController) { ++ super(priority); ++ this.world = world; ++ this.x = x; ++ this.z = z; ++ this.taskController = taskController; ++ } ++ ++ @Override ++ public String toString() { ++ return "Task for world: '" + this.world.getWorld().getName() + "' at " + this.x + "," + this.z + ++ " poi: " + (this.taskController == this.world.poiDataController) + ", hash: " + this.hashCode(); ++ } ++ ++ /* ++ * ++ * IO thread will perform reads before writes ++ * ++ * How reads/writes are scheduled: ++ * ++ * If read in progress while scheduling write, ignore read and schedule write ++ * If read in progress while scheduling read (no write in progress), chain the read task ++ * ++ * ++ * If write in progress while scheduling read, use the pending write data and ret immediately ++ * If write in progress 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 ++ * ++ */ ++ ++ void reschedule(final int priority) { ++ // priority is checked before this stage // TODO what ++ this.queue.lazySet(null); ++ this.priority.lazySet(priority); ++ PaperFileIOThread.Holder.INSTANCE.queueTask(this); ++ } ++ ++ @Override ++ public void run() { ++ ChunkDataController.InProgressRead read = this.inProgressRead; ++ if (read != null) { ++ NBTTagCompound compound = PaperFileIOThread.FAILURE_VALUE; ++ try { ++ compound = this.taskController.readData(this.x, this.z); ++ } catch (final Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ LOGGER.fatal("Failed to read chunk data for task: " + this.toString(), thr); ++ // fall through to complete with null data ++ } ++ read.readFuture.complete(compound); ++ } ++ ++ final Long chunkKey = Long.valueOf(IOUtil.getCoordinateKey(this.x, this.z)); ++ ++ ChunkDataController.InProgressWrite write = this.inProgressWrite; ++ ++ if (write == null) { ++ // IntelliJ warns this is invalid, however it does not consider that writes to the task map & the inProgress field can occur concurrently. ++ ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final Long 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 == null ? null : valueInMap; ++ }); ++ ++ if (inMap == null) { ++ return; // set the task value to null, indicating we're done ++ } ++ ++ // not null, which means there was a concurrent write ++ write = this.inProgressWrite; ++ } ++ ++ // check if another process is writing ++ /*try { TODO: Can we restore this? ++ ((WorldServer)this.world).checkSession(); ++ } catch (final Exception ex) { ++ LOGGER.fatal("Couldn't save chunk; already in use by another instance of Minecraft?", ex); ++ // we don't need to set the write counter to -1 as we know at this stage there's no point in re-scheduling ++ // writes since they'll fail anyways. ++ return; ++ } ++*/ ++ for (;;) { ++ final long writeCounter; ++ final NBTTagCompound data; ++ ++ //noinspection SynchronizationOnLocalVariableOrMethodParameter ++ synchronized (write) { ++ writeCounter = write.writeCounter; ++ data = write.data; ++ } ++ ++ boolean failedWrite = false; ++ ++ try { ++ this.taskController.writeData(this.x, this.z, data); ++ } catch (final Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ LOGGER.fatal("Failed to write chunk data for task: " + this.toString(), thr); ++ failedWrite = true; ++ } ++ ++ boolean finalFailWrite = failedWrite; ++ ++ ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final Long 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.writeCounter == writeCounter) { ++ if (finalFailWrite) { ++ valueInMap.inProgressWrite.writeCounter = -1L; ++ } ++ ++ return null; ++ } ++ return valueInMap; ++ // Hack end ++ }); ++ ++ if (inMap == null) { ++ // write counter matched, so we wrote the most up-to-date pending data, we're done here ++ // or we failed to write and successfully set the write counter to -1 ++ return; // we're done here ++ } ++ ++ // fetch & write new data ++ continue; ++ } ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java b/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..97f2e433c483f1ebd7500ae142269e144ef5fda4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java +@@ -0,0 +1,277 @@ ++package com.destroystokyo.paper.io; ++ ++import java.util.concurrent.ConcurrentLinkedQueue; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.atomic.AtomicReference; ++ ++public class PrioritizedTaskQueue { ++ ++ // lower numbers are a higher priority (except < 0) ++ // higher priorities are always executed before lower priorities ++ ++ /** ++ * Priority value indicating the task has completed or is being completed. ++ */ ++ public static final int COMPLETING_PRIORITY = -1; ++ ++ /** ++ * Highest priority, should only be used for main thread tasks or tasks that are blocking the main thread. ++ */ ++ public static final int HIGHEST_PRIORITY = 0; ++ ++ /** ++ * Should be only used in an IO task so that chunk loads do not wait on other IO tasks. ++ * This only exists because IO tasks are scheduled before chunk load tasks to decrease IO waiting times. ++ */ ++ public static final int HIGHER_PRIORITY = 1; ++ ++ /** ++ * Should be used for scheduling chunk loads/generation that would increase response times to users. ++ */ ++ public static final int HIGH_PRIORITY = 2; ++ ++ /** ++ * Default priority. ++ */ ++ public static final int NORMAL_PRIORITY = 3; ++ ++ /** ++ * Use for tasks not at all critical and can potentially be delayed. ++ */ ++ public static final int LOW_PRIORITY = 4; ++ ++ /** ++ * Use for tasks that should "eventually" execute. ++ */ ++ public static final int LOWEST_PRIORITY = 5; ++ ++ private static final int TOTAL_PRIORITIES = 6; ++ ++ final ConcurrentLinkedQueue[] queues = (ConcurrentLinkedQueue[])new ConcurrentLinkedQueue[TOTAL_PRIORITIES]; ++ ++ private final AtomicBoolean shutdown = new AtomicBoolean(); ++ ++ { ++ for (int i = 0; i < TOTAL_PRIORITIES; ++i) { ++ this.queues[i] = new ConcurrentLinkedQueue<>(); ++ } ++ } ++ ++ /** ++ * Returns whether the specified priority is valid ++ */ ++ public static boolean validPriority(final int priority) { ++ return priority >= 0 && priority < TOTAL_PRIORITIES; ++ } ++ ++ /** ++ * Queues a task. ++ * @throws IllegalStateException If the task has already been queued. Use {@link PrioritizedTask#raisePriority(int)} to ++ * raise a task's priority. ++ * This can also be thrown if the queue has shutdown. ++ */ ++ public void add(final T task) throws IllegalStateException { ++ int priority = task.getPriority(); ++ if (priority != COMPLETING_PRIORITY) { ++ task.setQueue(this); ++ this.queues[priority].add(task); ++ } ++ if (this.shutdown.get()) { ++ // note: we're not actually sure at this point if our task will go through ++ throw new IllegalStateException("Queue has shutdown, refusing to execute task " + IOUtil.genericToString(task)); ++ } ++ } ++ ++ /** ++ * Polls the highest priority task currently available. {@code null} if none. ++ */ ++ public T poll() { ++ T task; ++ for (int i = 0; i < TOTAL_PRIORITIES; ++i) { ++ final ConcurrentLinkedQueue queue = this.queues[i]; ++ ++ while ((task = queue.poll()) != null) { ++ final int prevPriority = task.tryComplete(i); ++ if (prevPriority != COMPLETING_PRIORITY && prevPriority <= i) { ++ // if the prev priority was greater-than or equal to our current priority ++ return task; ++ } ++ } ++ } ++ ++ return null; ++ } ++ ++ /** ++ * Returns whether this queue may have tasks queued. ++ *

    ++ * This operation is not atomic, but is MT-Safe. ++ *

    ++ * @return {@code true} if tasks may be queued, {@code false} otherwise ++ */ ++ public boolean hasTasks() { ++ for (int i = 0; i < TOTAL_PRIORITIES; ++i) { ++ final ConcurrentLinkedQueue queue = this.queues[i]; ++ ++ if (queue.peek() != null) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ /** ++ * Prevent further additions to this queue. Attempts to add after this call has completed (potentially during) will ++ * result in {@link IllegalStateException} being thrown. ++ *

    ++ * This operation is atomic with respect to other shutdown calls ++ *

    ++ *

    ++ * After this call has completed, regardless of return value, this queue will be shutdown. ++ *

    ++ * @return {@code true} if the queue was shutdown, {@code false} if it has shut down already ++ */ ++ public boolean shutdown() { ++ return this.shutdown.getAndSet(false); ++ } ++ ++ public abstract static class PrioritizedTask { ++ ++ protected final AtomicReference queue = new AtomicReference<>(); ++ ++ protected final AtomicInteger priority; ++ ++ protected PrioritizedTask() { ++ this(PrioritizedTaskQueue.NORMAL_PRIORITY); ++ } ++ ++ protected PrioritizedTask(final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.priority = new AtomicInteger(priority); ++ } ++ ++ /** ++ * Returns the current priority. Note that {@link PrioritizedTaskQueue#COMPLETING_PRIORITY} will be returned ++ * if this task is completing or has completed. ++ */ ++ public final int getPriority() { ++ return this.priority.get(); ++ } ++ ++ /** ++ * Returns whether this task is scheduled to execute, or has been already executed. ++ */ ++ public boolean isScheduled() { ++ return this.queue.get() != null; ++ } ++ ++ final int tryComplete(final int minPriority) { ++ for (int curr = this.getPriorityVolatile();;) { ++ if (curr == COMPLETING_PRIORITY) { ++ return COMPLETING_PRIORITY; ++ } ++ if (curr > minPriority) { ++ // curr is lower priority ++ return curr; ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, COMPLETING_PRIORITY))) { ++ return curr; ++ } ++ continue; ++ } ++ } ++ ++ /** ++ * Forces this task to be completed. ++ * @return {@code true} if the task was cancelled, {@code false} if the task has already completed or is being completed. ++ */ ++ public boolean cancel() { ++ return this.exchangePriorityVolatile(PrioritizedTaskQueue.COMPLETING_PRIORITY) != PrioritizedTaskQueue.COMPLETING_PRIORITY; ++ } ++ ++ /** ++ * Attempts to raise the priority to the priority level specified. ++ * @param priority Priority specified ++ * @return {@code true} if successful, {@code false} otherwise. ++ */ ++ public boolean raisePriority(final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority"); ++ } ++ ++ for (int curr = this.getPriorityVolatile();;) { ++ if (curr == COMPLETING_PRIORITY) { ++ return false; ++ } ++ if (priority >= curr) { ++ return true; ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority))) { ++ PrioritizedTaskQueue queue = this.queue.get(); ++ if (queue != null) { ++ //noinspection unchecked ++ queue.queues[priority].add(this); // silently fail on shutdown ++ } ++ return true; ++ } ++ continue; ++ } ++ } ++ ++ /** ++ * Attempts to set this task's priority level to the level specified. ++ * @param priority Specified priority level. ++ * @return {@code true} if successful, {@code false} if this task is completing or has completed. ++ */ ++ public boolean updatePriority(final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority"); ++ } ++ ++ for (int curr = this.getPriorityVolatile();;) { ++ if (curr == COMPLETING_PRIORITY) { ++ return false; ++ } ++ if (curr == priority) { ++ return true; ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority))) { ++ PrioritizedTaskQueue queue = this.queue.get(); ++ if (queue != null) { ++ //noinspection unchecked ++ queue.queues[priority].add(this); // silently fail on shutdown ++ } ++ return true; ++ } ++ continue; ++ } ++ } ++ ++ void setQueue(final PrioritizedTaskQueue queue) { ++ this.queue.set(queue); ++ } ++ ++ /* priority */ ++ ++ protected final int getPriorityVolatile() { ++ return this.priority.get(); ++ } ++ ++ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { ++ if (this.priority.compareAndSet(expect, update)) { ++ return expect; ++ } ++ return this.priority.get(); ++ } ++ ++ protected final int exchangePriorityVolatile(final int value) { ++ return this.priority.getAndSet(value); ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java b/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ee906b594b306906c170180a29a8b61997d05168 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java +@@ -0,0 +1,241 @@ ++package com.destroystokyo.paper.io; ++ ++import net.minecraft.server.MinecraftServer; ++import org.apache.logging.log4j.Logger; ++ ++import java.util.concurrent.ConcurrentLinkedQueue; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.locks.LockSupport; ++ ++public class QueueExecutorThread extends Thread { ++ ++ private static final Logger LOGGER = MinecraftServer.LOGGER; ++ ++ protected final PrioritizedTaskQueue queue; ++ protected final long spinWaitTime; ++ ++ protected volatile boolean closed; ++ ++ protected final AtomicBoolean parked = new AtomicBoolean(); ++ ++ protected volatile ConcurrentLinkedQueue flushQueue = new ConcurrentLinkedQueue<>(); ++ protected volatile long flushCycles; ++ ++ public QueueExecutorThread(final PrioritizedTaskQueue queue) { ++ this(queue, (int)(1.e6)); // 1.0ms ++ } ++ ++ public QueueExecutorThread(final PrioritizedTaskQueue queue, final long spinWaitTime) { // in ms ++ this.queue = queue; ++ this.spinWaitTime = spinWaitTime; ++ } ++ ++ @Override ++ public void run() { ++ final long spinWaitTime = this.spinWaitTime; ++ main_loop: ++ for (;;) { ++ this.pollTasks(true); ++ ++ // spinwait ++ ++ final long start = System.nanoTime(); ++ ++ for (;;) { ++ // If we are interrpted for any reason, park() will always return immediately. Clear so that we don't needlessly use cpu in such an event. ++ Thread.interrupted(); ++ LockSupport.parkNanos("Spinwaiting on tasks", 1000L); // 1us ++ ++ if (this.pollTasks(true)) { ++ // restart loop, found tasks ++ continue main_loop; ++ } ++ ++ if (this.handleClose()) { ++ return; // we're done ++ } ++ ++ if ((System.nanoTime() - start) >= spinWaitTime) { ++ break; ++ } ++ } ++ ++ if (this.handleClose()) { ++ return; ++ } ++ ++ this.parked.set(true); ++ ++ // We need to parse here to avoid a race condition where a thread queues a task before we set parked to true ++ // (i.e it will not notify us) ++ if (this.pollTasks(true)) { ++ this.parked.set(false); ++ continue; ++ } ++ ++ if (this.handleClose()) { ++ return; ++ } ++ ++ // we don't need to check parked before sleeping, but we do need to check parked in a do-while loop ++ // LockSupport.park() can fail for any reason ++ do { ++ Thread.interrupted(); ++ LockSupport.park("Waiting on tasks"); ++ } while (this.parked.get()); ++ } ++ } ++ ++ protected boolean handleClose() { ++ if (this.closed) { ++ this.pollTasks(true); // this ensures we've emptied the queue ++ this.handleFlushThreads(true); ++ return true; ++ } ++ return false; ++ } ++ ++ protected boolean pollTasks(boolean flushTasks) { ++ Runnable task; ++ boolean ret = false; ++ ++ while ((task = this.queue.poll()) != null) { ++ ret = true; ++ try { ++ task.run(); ++ } catch (final Throwable throwable) { ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ LOGGER.fatal("Exception thrown from prioritized runnable task in thread '" + this.getName() + "': " + IOUtil.genericToString(task), throwable); ++ } ++ } ++ ++ if (flushTasks) { ++ this.handleFlushThreads(false); ++ } ++ ++ return ret; ++ } ++ ++ protected void handleFlushThreads(final boolean shutdown) { ++ Thread parking; ++ ConcurrentLinkedQueue flushQueue = this.flushQueue; ++ do { ++ ++flushCycles; // may be plain read opaque write ++ while ((parking = flushQueue.poll()) != null) { ++ LockSupport.unpark(parking); ++ } ++ } while (this.pollTasks(false)); ++ ++ if (shutdown) { ++ this.flushQueue = null; ++ ++ // defend against a race condition where a flush thread double-checks right before we set to null ++ while ((parking = flushQueue.poll()) != null) { ++ LockSupport.unpark(parking); ++ } ++ } ++ } ++ ++ /** ++ * Notify's this thread that a task has been added to its queue ++ * @return {@code true} if this thread was waiting for tasks, {@code false} if it is executing tasks ++ */ ++ public boolean notifyTasks() { ++ if (this.parked.get() && this.parked.getAndSet(false)) { ++ LockSupport.unpark(this); ++ return true; ++ } ++ return false; ++ } ++ ++ protected void queueTask(final T task) { ++ this.queue.add(task); ++ this.notifyTasks(); ++ } ++ ++ /** ++ * Waits until this thread's queue is empty. ++ * ++ * @throws IllegalStateException If the current thread is {@code this} thread. ++ */ ++ public void flush() { ++ final Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread == this) { ++ // avoid deadlock ++ throw new IllegalStateException("Cannot flush the queue executor thread while on the queue executor thread"); ++ } ++ ++ // order is important ++ ++ int successes = 0; ++ long lastCycle = -1L; ++ ++ do { ++ final ConcurrentLinkedQueue flushQueue = this.flushQueue; ++ if (flushQueue == null) { ++ return; ++ } ++ ++ flushQueue.add(currentThread); ++ ++ // double check flush queue ++ if (this.flushQueue == null) { ++ return; ++ } ++ ++ final long currentCycle = this.flushCycles; // may be opaque read ++ ++ if (currentCycle == lastCycle) { ++ Thread.yield(); ++ continue; ++ } ++ ++ // force response ++ this.parked.set(false); ++ LockSupport.unpark(this); ++ ++ LockSupport.park("flushing queue executor thread"); ++ ++ // returns whether there are tasks queued, does not return whether there are tasks executing ++ // this is why we cycle twice twice through flush (we know a pollTask call is made after a flush cycle) ++ // we really only need to guarantee that the tasks this thread has queued has gone through, and can leave ++ // tasks queued concurrently that are unsychronized with this thread as undefined behavior ++ if (this.queue.hasTasks()) { ++ successes = 0; ++ } else { ++ ++successes; ++ } ++ ++ } while (successes != 2); ++ ++ } ++ ++ /** ++ * Closes this queue executor's queue and optionally waits for it to empty. ++ *

    ++ * If wait is {@code true}, then the queue will be empty by the time this call completes. ++ *

    ++ *

    ++ * This function is MT-Safe. ++ *

    ++ * @param wait If this call is to wait until the queue is empty ++ * @param killQueue Whether to shutdown this thread's queue ++ * @return whether this thread shut down the queue ++ */ ++ public boolean close(final boolean wait, final boolean killQueue) { ++ boolean ret = !killQueue ? false : this.queue.shutdown(); ++ this.closed = true; ++ ++ // force thread to respond to the shutdown ++ this.parked.set(false); ++ LockSupport.unpark(this); ++ ++ if (wait) { ++ this.flush(); ++ } ++ return ret; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkLoadTask.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkLoadTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9a2b51d005efc9d31b3685e8298fd00b341c7dc7 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkLoadTask.java +@@ -0,0 +1,146 @@ ++package com.destroystokyo.paper.io.chunk; ++ ++import co.aikar.timings.Timing; ++import com.destroystokyo.paper.io.PaperFileIOThread; ++import com.destroystokyo.paper.io.IOUtil; ++import net.minecraft.server.level.PlayerChunkMap; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.chunk.storage.ChunkRegionLoader; ++ ++import java.util.ArrayDeque; ++import java.util.function.Consumer; ++ ++public final class ChunkLoadTask extends ChunkTask { ++ ++ public boolean cancelled; ++ ++ Consumer onComplete; ++ public PaperFileIOThread.ChunkData chunkData; ++ ++ private boolean hasCompleted; ++ ++ public ChunkLoadTask(final WorldServer world, final int chunkX, final int chunkZ, final int priority, ++ final ChunkTaskManager taskManager, ++ final Consumer onComplete) { ++ super(world, chunkX, chunkZ, priority, taskManager); ++ this.onComplete = onComplete; ++ } ++ ++ private static final ArrayDeque EMPTY_QUEUE = new ArrayDeque<>(); ++ ++ private static ChunkRegionLoader.InProgressChunkHolder createEmptyHolder() { ++ return new ChunkRegionLoader.InProgressChunkHolder(null, EMPTY_QUEUE); ++ } ++ ++ @Override ++ public void run() { ++ try { ++ this.executeTask(); ++ } catch (final Throwable ex) { ++ PaperFileIOThread.LOGGER.error("Failed to execute chunk load task: " + this.toString(), ex); ++ if (!this.hasCompleted) { ++ this.complete(ChunkLoadTask.createEmptyHolder()); ++ } ++ } ++ } ++ ++ private boolean checkCancelled() { ++ if (this.cancelled) { ++ // IntelliJ does not understand writes may occur to cancelled concurrently. ++ return this.taskManager.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(this.chunkX, this.chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> { ++ if (valueInMap != ChunkLoadTask.this) { ++ throw new IllegalStateException("Expected this task to be scheduled, but another was! Other: " + valueInMap + ", current: " + ChunkLoadTask.this); ++ } ++ ++ if (valueInMap.cancelled) { ++ return null; ++ } ++ return valueInMap; ++ }) == null; ++ } ++ return false; ++ } ++ ++ public void executeTask() { ++ if (this.checkCancelled()) { ++ return; ++ } ++ ++ // either executed synchronously or asynchronously ++ final PaperFileIOThread.ChunkData chunkData = this.chunkData; ++ ++ if (chunkData.poiData == PaperFileIOThread.FAILURE_VALUE || chunkData.chunkData == PaperFileIOThread.FAILURE_VALUE) { ++ PaperFileIOThread.LOGGER.error("Could not load chunk for task: " + this.toString() + ", file IO thread has dumped the relevant exception above"); ++ this.complete(ChunkLoadTask.createEmptyHolder()); ++ return; ++ } ++ ++ if (chunkData.chunkData == null) { ++ // not on disk ++ this.complete(ChunkLoadTask.createEmptyHolder()); ++ return; ++ } ++ ++ final ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(this.chunkX, this.chunkZ); ++ ++ final PlayerChunkMap chunkManager = this.world.getChunkProvider().playerChunkMap; ++ ++ try (Timing ignored = this.world.timings.chunkLoadLevelTimer.startTimingIfSync()) { ++ final ChunkRegionLoader.InProgressChunkHolder chunkHolder; ++ ++ // apply fixes ++ ++ try { ++ chunkData.chunkData = chunkManager.getChunkData(this.world.getTypeKey(), ++ chunkManager.getWorldPersistentDataSupplier(), chunkData.chunkData, chunkPos, this.world); // clone data for safety, file IO thread does not clone ++ } catch (final Throwable ex) { ++ PaperFileIOThread.LOGGER.error("Could not apply datafixers for chunk task: " + this.toString(), ex); ++ this.complete(ChunkLoadTask.createEmptyHolder()); ++ } ++ ++ if (this.checkCancelled()) { ++ return; ++ } ++ ++ try { ++ this.world.getChunkProvider().playerChunkMap.updateChunkStatusOnDisk(chunkPos, chunkData.chunkData); ++ } catch (final Throwable ex) { ++ PaperFileIOThread.LOGGER.warn("Failed to update chunk status cache for task: " + this.toString(), ex); ++ // non-fatal, continue ++ } ++ ++ try { ++ chunkHolder = ChunkRegionLoader.loadChunk(this.world, ++ chunkManager.definedStructureManager, chunkManager.getVillagePlace(), chunkPos, ++ chunkData.chunkData, true); ++ } catch (final Throwable ex) { ++ PaperFileIOThread.LOGGER.error("Could not de-serialize chunk data for task: " + this.toString(), ex); ++ this.complete(ChunkLoadTask.createEmptyHolder()); ++ return; ++ } ++ ++ this.complete(chunkHolder); ++ } ++ } ++ ++ private void complete(final ChunkRegionLoader.InProgressChunkHolder holder) { ++ this.hasCompleted = true; ++ holder.poiData = this.chunkData == null ? null : this.chunkData.poiData; ++ ++ this.taskManager.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(this.chunkX, this.chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> { ++ if (valueInMap != ChunkLoadTask.this) { ++ throw new IllegalStateException("Expected this task to be scheduled, but another was! Other: " + valueInMap + ", current: " + ChunkLoadTask.this); ++ } ++ if (valueInMap.cancelled) { ++ return null; ++ } ++ try { ++ ChunkLoadTask.this.onComplete.accept(holder); ++ } catch (final Throwable thr) { ++ PaperFileIOThread.LOGGER.error("Failed to complete chunk data for task: " + this.toString(), thr); ++ } ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkSaveTask.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkSaveTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e3ad8f50b51c4e9bf38ffa5911444cc88d3f67eb +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkSaveTask.java +@@ -0,0 +1,111 @@ ++package com.destroystokyo.paper.io.chunk; ++ ++import co.aikar.timings.Timing; ++import com.destroystokyo.paper.io.PaperFileIOThread; ++import com.destroystokyo.paper.io.IOUtil; ++import com.destroystokyo.paper.io.PrioritizedTaskQueue; ++ ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.atomic.AtomicInteger; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.chunk.storage.ChunkRegionLoader; ++ ++public final class ChunkSaveTask extends ChunkTask { ++ ++ public final ChunkRegionLoader.AsyncSaveData asyncSaveData; ++ public final IChunkAccess chunk; ++ public final CompletableFuture onComplete = new CompletableFuture<>(); ++ ++ private final AtomicInteger attemptedPriority; ++ ++ public ChunkSaveTask(final WorldServer world, final int chunkX, final int chunkZ, final int priority, ++ final ChunkTaskManager taskManager, final ChunkRegionLoader.AsyncSaveData asyncSaveData, ++ final IChunkAccess chunk) { ++ super(world, chunkX, chunkZ, priority, taskManager); ++ this.chunk = chunk; ++ this.asyncSaveData = asyncSaveData; ++ this.attemptedPriority = new AtomicInteger(priority); ++ } ++ ++ @Override ++ public void run() { ++ // can be executed asynchronously or synchronously ++ final NBTTagCompound compound; ++ ++ try (Timing ignored = this.world.timings.chunkUnloadDataSave.startTimingIfSync()) { ++ compound = ChunkRegionLoader.saveChunk(this.world, this.chunk, this.asyncSaveData); ++ } catch (final Throwable ex) { ++ // has a plugin modified something it should not have and made us CME? ++ PaperFileIOThread.LOGGER.error("Failed to serialize unloading chunk data for task: " + this.toString() + ", falling back to a synchronous execution", ex); ++ ++ // Note: We add to the server thread queue here since this is what the server will drain tasks from ++ // when waiting for chunks ++ ChunkTaskManager.queueChunkWaitTask(() -> { ++ try (Timing ignored = this.world.timings.chunkUnloadDataSave.startTiming()) { ++ NBTTagCompound data = PaperFileIOThread.FAILURE_VALUE; ++ ++ try { ++ data = ChunkRegionLoader.saveChunk(this.world, this.chunk, this.asyncSaveData); ++ PaperFileIOThread.LOGGER.info("Successfully serialized chunk data for task: " + this.toString() + " synchronously"); ++ } catch (final Throwable ex1) { ++ PaperFileIOThread.LOGGER.fatal("Failed to synchronously serialize unloading chunk data for task: " + this.toString() + "! Chunk data will be lost", ex1); ++ } ++ ++ ChunkSaveTask.this.complete(data); ++ } ++ }); ++ ++ return; // the main thread will now complete the data ++ } ++ ++ this.complete(compound); ++ } ++ ++ @Override ++ public boolean raisePriority(final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalStateException("Invalid priority: " + priority); ++ } ++ ++ // we know priority is valid here ++ for (int curr = this.attemptedPriority.get();;) { ++ if (curr <= priority) { ++ break; // curr is higher/same priority ++ } ++ if (this.attemptedPriority.compareAndSet(curr, priority)) { ++ break; ++ } ++ curr = this.attemptedPriority.get(); ++ } ++ ++ return super.raisePriority(priority); ++ } ++ ++ @Override ++ public boolean updatePriority(final int priority) { ++ if (!PrioritizedTaskQueue.validPriority(priority)) { ++ throw new IllegalStateException("Invalid priority: " + priority); ++ } ++ this.attemptedPriority.set(priority); ++ return super.updatePriority(priority); ++ } ++ ++ private void complete(final NBTTagCompound compound) { ++ try { ++ this.onComplete.complete(compound); ++ } catch (final Throwable thr) { ++ PaperFileIOThread.LOGGER.error("Failed to complete chunk data for task: " + this.toString(), thr); ++ } ++ if (compound != PaperFileIOThread.FAILURE_VALUE) { ++ PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, this.chunkX, this.chunkZ, null, compound, this.attemptedPriority.get()); ++ } ++ this.taskManager.chunkSaveTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(this.chunkX, this.chunkZ)), (final Long keyInMap, final ChunkSaveTask valueInMap) -> { ++ if (valueInMap != ChunkSaveTask.this) { ++ throw new IllegalStateException("Expected this task to be scheduled, but another was! Other: " + valueInMap + ", this: " + ChunkSaveTask.this); ++ } ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTask.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9c1370c7d85262da9d64871e03e5a8a8c5e087d7 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTask.java +@@ -0,0 +1,40 @@ ++package com.destroystokyo.paper.io.chunk; ++ ++import com.destroystokyo.paper.io.PaperFileIOThread; ++import com.destroystokyo.paper.io.PrioritizedTaskQueue; ++import net.minecraft.server.level.WorldServer; ++ ++abstract class ChunkTask extends PrioritizedTaskQueue.PrioritizedTask implements Runnable { ++ ++ public final WorldServer world; ++ public final int chunkX; ++ public final int chunkZ; ++ public final ChunkTaskManager taskManager; ++ ++ public ChunkTask(final WorldServer world, final int chunkX, final int chunkZ, final int priority, ++ final ChunkTaskManager taskManager) { ++ super(priority); ++ this.world = world; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.taskManager = taskManager; ++ } ++ ++ @Override ++ public String toString() { ++ return "Chunk task: class:" + this.getClass().getName() + ", for world '" + this.world.getWorld().getName() + ++ "', (" + this.chunkX + "," + this.chunkZ + "), hashcode:" + this.hashCode() + ", priority: " + this.getPriority(); ++ } ++ ++ @Override ++ public boolean raisePriority(final int priority) { ++ PaperFileIOThread.Holder.INSTANCE.bumpPriority(this.world, this.chunkX, this.chunkZ, priority); ++ return super.raisePriority(priority); ++ } ++ ++ @Override ++ public boolean updatePriority(final int priority) { ++ PaperFileIOThread.Holder.INSTANCE.setPriority(this.world, this.chunkX, this.chunkZ, priority); ++ return super.updatePriority(priority); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8e642f450b974d81f128d26edfd40915554db638 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java +@@ -0,0 +1,513 @@ ++package com.destroystokyo.paper.io.chunk; ++ ++import com.destroystokyo.paper.io.PaperFileIOThread; ++import com.destroystokyo.paper.io.IOUtil; ++import com.destroystokyo.paper.io.PrioritizedTaskQueue; ++import com.destroystokyo.paper.io.QueueExecutorThread; ++import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkProviderServer; ++import net.minecraft.server.level.PlayerChunk; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.util.thread.IAsyncTaskHandler; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.chunk.storage.ChunkRegionLoader; ++import org.apache.commons.lang.StringUtils; ++import org.apache.logging.log4j.Level; ++import org.bukkit.Bukkit; ++import org.spigotmc.AsyncCatcher; ++ ++import java.util.ArrayDeque; ++import java.util.HashSet; ++import java.util.Set; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.ConcurrentLinkedQueue; ++import java.util.function.Consumer; ++ ++public final class ChunkTaskManager { ++ ++ private final QueueExecutorThread[] workers; ++ private final WorldServer world; ++ ++ private final PrioritizedTaskQueue queue; ++ private final boolean perWorldQueue; ++ ++ final ConcurrentHashMap chunkLoadTasks = new ConcurrentHashMap<>(64, 0.5f); ++ final ConcurrentHashMap chunkSaveTasks = new ConcurrentHashMap<>(64, 0.5f); ++ ++ private final PrioritizedTaskQueue chunkTasks = new PrioritizedTaskQueue<>(); // used if async chunks are disabled in config ++ ++ protected static QueueExecutorThread[] globalWorkers; ++ protected static QueueExecutorThread globalUrgentWorker; ++ protected static PrioritizedTaskQueue globalQueue; ++ protected static PrioritizedTaskQueue globalUrgentQueue; ++ ++ protected static final ConcurrentLinkedQueue CHUNK_WAIT_QUEUE = new ConcurrentLinkedQueue<>(); ++ ++ public static final ArrayDeque WAITING_CHUNKS = new ArrayDeque<>(); // stack ++ ++ private static final class ChunkInfo { ++ ++ public final int chunkX; ++ public final int chunkZ; ++ public final WorldServer world; ++ ++ public ChunkInfo(final int chunkX, final int chunkZ, final WorldServer 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 WorldServer 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(); ++ } ++ } ++ ++ private static ChunkInfo[] getChunkInfos() { ++ ChunkInfo[] chunks; ++ synchronized (WAITING_CHUNKS) { ++ chunks = WAITING_CHUNKS.toArray(new ChunkInfo[0]); ++ } ++ return chunks; ++ } ++ ++ public static void dumpAllChunkLoadInfo() { ++ ChunkInfo[] chunks = getChunkInfos(); ++ if (chunks.length > 0) { ++ PaperFileIOThread.LOGGER.log(Level.ERROR, "Chunk wait task info below: "); ++ ++ for (final ChunkInfo chunkInfo : chunks) { ++ final long key = IOUtil.getCoordinateKey(chunkInfo.chunkX, chunkInfo.chunkZ); ++ final ChunkLoadTask loadTask = chunkInfo.world.asyncChunkTaskManager.chunkLoadTasks.get(key); ++ final ChunkSaveTask saveTask = chunkInfo.world.asyncChunkTaskManager.chunkSaveTasks.get(key); ++ ++ PaperFileIOThread.LOGGER.log(Level.ERROR, chunkInfo.chunkX + "," + chunkInfo.chunkZ + " in '" + chunkInfo.world.getWorld().getName() + ":"); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, "Load Task - " + (loadTask == null ? "none" : loadTask.toString())); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, "Save Task - " + (saveTask == null ? "none" : saveTask.toString())); ++ // log current status of chunk to indicate whether we're waiting on generation or loading ++ PlayerChunk chunkHolder = chunkInfo.world.getChunkProvider().playerChunkMap.getVisibleChunk(key); ++ ++ dumpChunkInfo(new HashSet<>(), chunkHolder, chunkInfo.chunkX, chunkInfo.chunkZ); ++ } ++ } ++ } ++ ++ static void dumpChunkInfo(Set seenChunks, PlayerChunk chunkHolder, int x, int z) { ++ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1); ++ } ++ ++ static void dumpChunkInfo(Set seenChunks, PlayerChunk chunkHolder, int x, int z, int indent, int maxDepth) { ++ if (seenChunks.contains(chunkHolder)) { ++ return; ++ } ++ if (indent > maxDepth) { ++ return; ++ } ++ seenChunks.add(chunkHolder); ++ String indentStr = StringUtils.repeat(" ", indent); ++ if (chunkHolder == null) { ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder - null for (" + x +"," + z +")"); ++ } else { ++ IChunkAccess chunk = chunkHolder.getAvailableChunkNow(); ++ ChunkStatus holderStatus = chunkHolder.getChunkHolderStatus(); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder - non-null"); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Status - " + ((chunk == null) ? "null chunk" : chunk.getChunkStatus().toString())); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Ticket Status - " + PlayerChunk.getChunkStatus(chunkHolder.getTicketLevel())); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Status - " + ((holderStatus == null) ? "null" : holderStatus.toString())); ++ } ++ } ++ ++ public static void initGlobalLoadThreads(int threads) { ++ if (threads <= 0 || globalWorkers != null) { ++ return; ++ } ++ ++ globalWorkers = new QueueExecutorThread[threads]; ++ globalQueue = new PrioritizedTaskQueue<>(); ++ globalUrgentQueue = new PrioritizedTaskQueue<>(); ++ ++ for (int i = 0; i < threads; ++i) { ++ globalWorkers[i] = new QueueExecutorThread<>(globalQueue, (long)0.10e6); //0.1ms ++ globalWorkers[i].setName("Paper Async Chunk Task Thread #" + i); ++ globalWorkers[i].setPriority(Thread.NORM_PRIORITY - 1); ++ globalWorkers[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { ++ PaperFileIOThread.LOGGER.fatal("Thread '" + thread.getName() + "' threw an uncaught exception!", throwable); ++ }); ++ ++ globalWorkers[i].start(); ++ } ++ ++ globalUrgentWorker = new QueueExecutorThread<>(globalUrgentQueue, (long)0.10e6); //0.1ms ++ globalUrgentWorker.setName("Paper Async Chunk Urgent Task Thread"); ++ globalUrgentWorker.setPriority(Thread.NORM_PRIORITY+1); ++ globalUrgentWorker.setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { ++ PaperFileIOThread.LOGGER.fatal("Thread '" + thread.getName() + "' threw an uncaught exception!", throwable); ++ }); ++ ++ globalUrgentWorker.start(); ++ } ++ ++ /** ++ * Creates this chunk task manager to operate off the specified number of threads. If the specified number of threads is ++ * less-than or equal to 0, then this chunk task manager will operate off of the world's chunk task queue. ++ * @param world Specified world. ++ * @param threads Specified number of threads. ++ * @see ChunkProviderServer#serverThreadQueue ++ */ ++ public ChunkTaskManager(final WorldServer world, final int threads) { ++ this.world = world; ++ this.workers = threads <= 0 ? null : new QueueExecutorThread[threads]; ++ this.queue = new PrioritizedTaskQueue<>(); ++ this.perWorldQueue = true; ++ ++ for (int i = 0; i < threads; ++i) { ++ this.workers[i] = new QueueExecutorThread<>(this.queue, (long)0.10e6); //0.1ms ++ this.workers[i].setName("Async chunk loader thread #" + i + " for world: " + world.getWorld().getName()); ++ this.workers[i].setPriority(Thread.NORM_PRIORITY - 1); ++ this.workers[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { ++ PaperFileIOThread.LOGGER.fatal("Thread '" + thread.getName() + "' threw an uncaught exception!", throwable); ++ }); ++ ++ this.workers[i].start(); ++ } ++ } ++ ++ /** ++ * Creates the chunk task manager to work from the global workers. When {@link #close(boolean)} is invoked, ++ * the global queue is not shutdown. If the global workers is configured to be disabled or use 0 threads, then ++ * this chunk task manager will operate off of the world's chunk task queue. ++ * @param world The world that this task manager is responsible for ++ * @see ChunkProviderServer#serverThreadQueue ++ */ ++ public ChunkTaskManager(final WorldServer world) { ++ this.world = world; ++ this.workers = globalWorkers; ++ this.queue = globalQueue; ++ this.perWorldQueue = false; ++ } ++ ++ public boolean pollNextChunkTask() { ++ final ChunkTask task = this.chunkTasks.poll(); ++ ++ if (task != null) { ++ task.run(); ++ return true; ++ } ++ return false; ++ } ++ ++ /** ++ * Polls and runs the next available chunk wait queue task. This is to be used when the server is waiting on a chunk queue. ++ * (per-world can cause issues if all the worker threads are blocked waiting for a response from the main thread) ++ */ ++ public static boolean pollChunkWaitQueue() { ++ final Runnable run = CHUNK_WAIT_QUEUE.poll(); ++ if (run != null) { ++ run.run(); ++ return true; ++ } ++ return false; ++ } ++ ++ /** ++ * Queues a chunk wait task. Note that this will execute out of order with respect to tasks scheduled on a world's ++ * chunk task queue, since this is the global chunk wait queue. ++ */ ++ public static void queueChunkWaitTask(final Runnable runnable) { ++ CHUNK_WAIT_QUEUE.add(runnable); ++ } ++ ++ private static void drainChunkWaitQueue() { ++ Runnable run; ++ while ((run = CHUNK_WAIT_QUEUE.poll()) != null) { ++ run.run(); ++ } ++ } ++ ++ /** ++ * The exact same as {@link #scheduleChunkLoad(int, int, int, Consumer, boolean)}, except that the chunk data is provided as ++ * the {@code data} parameter. ++ */ ++ public ChunkLoadTask scheduleChunkLoad(final int chunkX, final int chunkZ, final int priority, ++ final Consumer onComplete, ++ final boolean intendingToBlock, final CompletableFuture dataFuture) { ++ final WorldServer world = this.world; ++ ++ return this.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> { ++ if (valueInMap != null) { ++ if (!valueInMap.cancelled) { ++ throw new IllegalStateException("Double scheduling chunk load for task: " + valueInMap.toString()); ++ } ++ valueInMap.cancelled = false; ++ valueInMap.onComplete = onComplete; ++ return valueInMap; ++ } ++ ++ final ChunkLoadTask ret = new ChunkLoadTask(world, chunkX, chunkZ, priority, ChunkTaskManager.this, onComplete); ++ ++ dataFuture.thenAccept((final NBTTagCompound data) -> { ++ final boolean failed = data == PaperFileIOThread.FAILURE_VALUE; ++ PaperFileIOThread.Holder.INSTANCE.loadChunkDataAsync(world, chunkX, chunkZ, priority, (final PaperFileIOThread.ChunkData chunkData) -> { ++ ret.chunkData = chunkData; ++ if (!failed) { ++ chunkData.chunkData = data; ++ } ++ ChunkTaskManager.this.internalSchedule(ret); // only schedule to the worker threads here ++ }, true, failed, intendingToBlock); // read data off disk if the future fails ++ }); ++ ++ return ret; ++ }); ++ } ++ ++ public void cancelChunkLoad(final int chunkX, final int chunkZ) { ++ this.chunkLoadTasks.compute(IOUtil.getCoordinateKey(chunkX, chunkZ), (final Long keyInMap, final ChunkLoadTask valueInMap) -> { ++ if (valueInMap == null) { ++ return null; ++ } ++ ++ if (valueInMap.cancelled) { ++ PaperFileIOThread.LOGGER.warn("Task " + valueInMap.toString() + " is already cancelled!"); ++ } ++ valueInMap.cancelled = true; ++ if (valueInMap.cancel()) { ++ return null; ++ } ++ ++ return valueInMap; ++ }); ++ } ++ ++ /** ++ * Schedules an asynchronous chunk load for the specified coordinates. The onComplete parameter may be invoked asynchronously ++ * on a worker thread or on the world's chunk executor queue. As such the code that is executed for the parameter should be ++ * carefully chosen. ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param priority Priority for this task ++ * @param onComplete The consumer to invoke with the {@link ChunkRegionLoader.InProgressChunkHolder} object once this task is complete ++ * @param intendingToBlock Whether the caller is intending to block on this task completing (this is a performance tune, and has no adverse side-effects) ++ * @return The {@link ChunkLoadTask} associated with ++ */ ++ public ChunkLoadTask scheduleChunkLoad(final int chunkX, final int chunkZ, final int priority, ++ final Consumer onComplete, ++ final boolean intendingToBlock) { ++ final WorldServer world = this.world; ++ ++ return this.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> { ++ if (valueInMap != null) { ++ if (!valueInMap.cancelled) { ++ throw new IllegalStateException("Double scheduling chunk load for task: " + valueInMap.toString()); ++ } ++ valueInMap.cancelled = false; ++ valueInMap.onComplete = onComplete; ++ return valueInMap; ++ } ++ ++ final ChunkLoadTask ret = new ChunkLoadTask(world, chunkX, chunkZ, priority, ChunkTaskManager.this, onComplete); ++ ++ PaperFileIOThread.Holder.INSTANCE.loadChunkDataAsync(world, chunkX, chunkZ, priority, (final PaperFileIOThread.ChunkData chunkData) -> { ++ ret.chunkData = chunkData; ++ ChunkTaskManager.this.internalSchedule(ret); // only schedule to the worker threads here ++ }, true, true, intendingToBlock); ++ ++ return ret; ++ }); ++ } ++ ++ /** ++ * Schedules an async save for the specified chunk. The chunk, at the beginning of this call, must be completely unloaded ++ * from the world. ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param priority Priority for this task ++ * @param asyncSaveData Async save data. See {@link ChunkRegionLoader#getAsyncSaveData(WorldServer, IChunkAccess)} ++ * @param chunk Chunk to save ++ * @return The {@link ChunkSaveTask} associated with the save task. ++ */ ++ public ChunkSaveTask scheduleChunkSave(final int chunkX, final int chunkZ, final int priority, ++ final ChunkRegionLoader.AsyncSaveData asyncSaveData, ++ final IChunkAccess chunk) { ++ AsyncCatcher.catchOp("chunk save schedule"); ++ ++ final WorldServer world = this.world; ++ ++ return this.chunkSaveTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkSaveTask valueInMap) -> { ++ if (valueInMap != null) { ++ throw new IllegalStateException("Double scheduling chunk save for task: " + valueInMap.toString()); ++ } ++ ++ final ChunkSaveTask ret = new ChunkSaveTask(world, chunkX, chunkZ, priority, ChunkTaskManager.this, asyncSaveData, chunk); ++ ++ ChunkTaskManager.this.internalSchedule(ret); ++ ++ return ret; ++ }); ++ } ++ ++ /** ++ * Returns a completable future which will be completed with the un-copied chunk data for an in progress async save. ++ * Returns {@code null} if no save is in progress. ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ */ ++ public CompletableFuture getChunkSaveFuture(final int chunkX, final int chunkZ) { ++ final ChunkSaveTask chunkSaveTask = this.chunkSaveTasks.get(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ))); ++ if (chunkSaveTask == null) { ++ return null; ++ } ++ return chunkSaveTask.onComplete; ++ } ++ ++ /** ++ * Returns the chunk object being used to serialize data async for an unloaded chunk. Note that modifying this chunk ++ * is not safe to do as another thread is handling its save. The chunk is also not loaded into the world. ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @return Chunk object for an in-progress async save, or {@code null} if no save is in progress ++ */ ++ public IChunkAccess getChunkInSaveProgress(final int chunkX, final int chunkZ) { ++ final ChunkSaveTask chunkSaveTask = this.chunkSaveTasks.get(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ))); ++ if (chunkSaveTask == null) { ++ return null; ++ } ++ return chunkSaveTask.chunk; ++ } ++ ++ public void flush() { ++ // flush here since we schedule tasks on the IO thread that can schedule tasks here ++ drainChunkWaitQueue(); ++ PaperFileIOThread.Holder.INSTANCE.flush(); ++ drainChunkWaitQueue(); ++ ++ if (this.workers == null) { ++ if (Bukkit.isPrimaryThread() || MinecraftServer.getServer().hasStopped()) { ++ ((IAsyncTaskHandler)this.world.getChunkProvider().serverThreadQueue).executeAll(); ++ } else { ++ CompletableFuture wait = new CompletableFuture<>(); ++ MinecraftServer.getServer().scheduleOnMain(() -> { ++ ((IAsyncTaskHandler)this.world.getChunkProvider().serverThreadQueue).executeAll(); ++ }); ++ wait.join(); ++ } ++ } else { ++ for (final QueueExecutorThread worker : this.workers) { ++ worker.flush(); ++ } ++ } ++ if (globalUrgentWorker != null) globalUrgentWorker.flush(); ++ ++ // flush again since tasks we execute async saves ++ drainChunkWaitQueue(); ++ PaperFileIOThread.Holder.INSTANCE.flush(); ++ } ++ ++ public void close(final boolean wait) { ++ // flush here since we schedule tasks on the IO thread that can schedule tasks to this task manager ++ // we do this regardless of the wait param since after we invoke close no tasks can be queued ++ PaperFileIOThread.Holder.INSTANCE.flush(); ++ ++ if (this.workers == null) { ++ if (wait) { ++ this.flush(); ++ } ++ return; ++ } ++ ++ if (this.workers != globalWorkers) { ++ for (final QueueExecutorThread worker : this.workers) { ++ worker.close(false, this.perWorldQueue); ++ } ++ } ++ ++ if (wait) { ++ this.flush(); ++ } ++ } ++ ++ public void raisePriority(final int chunkX, final int chunkZ, final int priority) { ++ final Long chunkKey = Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)); ++ ++ ChunkTask chunkSaveTask = this.chunkSaveTasks.get(chunkKey); ++ if (chunkSaveTask != null) { ++ // don't bump save into urgent queue ++ raiseTaskPriority(chunkSaveTask, priority != PrioritizedTaskQueue.HIGHEST_PRIORITY ? priority : PrioritizedTaskQueue.HIGH_PRIORITY); ++ } ++ ++ ChunkLoadTask chunkLoadTask = this.chunkLoadTasks.get(chunkKey); ++ if (chunkLoadTask != null) { ++ raiseTaskPriority(chunkLoadTask, priority); ++ } ++ } ++ ++ private void raiseTaskPriority(ChunkTask task, int priority) { ++ final boolean raised = task.raisePriority(priority); ++ if (task.isScheduled() && raised && this.workers != null) { ++ // only notify if we're in queue to be executed ++ if (priority == PrioritizedTaskQueue.HIGHEST_PRIORITY) { ++ // was in another queue but became urgent later, add to urgent queue and the previous ++ // queue will just have to ignore this task if it has already been started. ++ // Ultimately, we now have 2 potential queues that can pull it out whoever gets it first ++ // but the urgent queue has dedicated thread(s) so it's likely to win.... ++ globalUrgentQueue.add(task); ++ this.internalScheduleNotifyUrgent(); ++ } else { ++ this.internalScheduleNotify(); ++ } ++ } ++ } ++ ++ protected void internalSchedule(final ChunkTask task) { ++ if (this.workers == null) { ++ this.chunkTasks.add(task); ++ return; ++ } ++ ++ // It's important we order the task to be executed before notifying. Avoid a race condition where the worker thread ++ // wakes up and goes to sleep before we actually schedule (or it's just about to sleep) ++ if (task.getPriority() == PrioritizedTaskQueue.HIGHEST_PRIORITY) { ++ globalUrgentQueue.add(task); ++ this.internalScheduleNotifyUrgent(); ++ } else { ++ this.queue.add(task); ++ this.internalScheduleNotify(); ++ } ++ ++ } ++ ++ protected void internalScheduleNotify() { ++ if (this.workers == null) { ++ return; ++ } ++ for (final QueueExecutorThread worker : this.workers) { ++ if (worker.notifyTasks()) { ++ // break here since we only want to wake up one worker for scheduling one task ++ break; ++ } ++ } ++ } ++ ++ ++ protected void internalScheduleNotifyUrgent() { ++ if (globalUrgentWorker == null) { ++ return; ++ } ++ globalUrgentWorker.notifyTasks(); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInTabComplete.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInTabComplete.java +index e201e4efd4ecc65ec3c38528a4ec5336e2d51ab2..45f3f8964a587c382b6ea82560e9da30be42987f 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInTabComplete.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInTabComplete.java +@@ -14,7 +14,7 @@ public class PacketPlayInTabComplete implements Packet { + @Override + public void a(PacketDataSerializer packetdataserializer) throws IOException { + this.a = packetdataserializer.i(); +- this.b = packetdataserializer.e(32500); ++ this.b = packetdataserializer.e(2048); + } + + @Override +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index fbd33aef21b4539d249c367609a36491530fb7ca..5a410550cfb48505c9de9979465ed1528c8fbf05 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -714,4 +714,9 @@ public final class MCUtil { + out.print(fileData); + } + } ++ ++ public static int getTicketLevelFor(ChunkStatus status) { ++ // TODO make sure the constant `33` is correct on future updates. See getChunkAt(int, int, ChunkStatus, boolean) ++ return 33 + ChunkStatus.getTicketLevelOffset(status); ++ } + } +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 191a74bd9b894f9d64d0a55747cb17e07ceef597..1732fc552c290d294b68d6f92f2a58d985fbef21 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -42,6 +42,7 @@ import net.minecraft.server.players.UserCache; + import net.minecraft.util.MathHelper; + import net.minecraft.util.datafix.DataConverterRegistry; + import net.minecraft.util.worldupdate.WorldUpgrader; ++import net.minecraft.world.entity.npc.VillagerTrades; + import net.minecraft.world.level.DataPackConfiguration; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.World; +@@ -210,6 +211,7 @@ public class Main { + + convertable_conversionsession.a((IRegistryCustom) iregistrycustom_dimension, (SaveData) object); + */ ++ Class.forName(VillagerTrades.class.getName());// Paper - load this sync so it won't fail later async + final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.a((thread) -> { + DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, datapackconfiguration1, thread, iregistrycustom_dimension, convertable_conversionsession, resourcepackrepository, datapackresources, null, dedicatedserversettings, DataConverterRegistry.a(), minecraftsessionservice, gameprofilerepository, usercache, WorldLoadListenerLogger::new); + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 9e6d70bdf4d1cf524e45b55ee51d87a30394ac84..baafdffabf28415da66eccb6e14466cd428f5832 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -921,7 +921,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant> getChunkAtAsynchronously(int x, int z, boolean gen, boolean isUrgent) { ++ if (Thread.currentThread() != this.serverThread) { ++ CompletableFuture> future = new CompletableFuture>(); ++ this.serverThreadQueue.execute(() -> { ++ this.getChunkAtAsynchronously(x, z, gen, isUrgent).whenComplete((chunk, ex) -> { ++ if (ex != null) { ++ future.completeExceptionally(ex); ++ } else { ++ future.complete(chunk); ++ } ++ }); ++ }); ++ return future; ++ } ++ ++ if (!com.destroystokyo.paper.PaperConfig.asyncChunks) { ++ world.getWorld().loadChunk(x, z, gen); ++ Chunk chunk = getChunkAtIfLoadedMainThread(x, z); ++ return CompletableFuture.completedFuture(chunk != null ? Either.left(chunk) : PlayerChunk.UNLOADED_CHUNK_ACCESS); ++ } ++ ++ long k = ChunkCoordIntPair.pair(x, z); ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(x, z); ++ ++ IChunkAccess ichunkaccess; ++ ++ // try cache ++ for (int l = 0; l < 4; ++l) { ++ if (k == this.cachePos[l] && ChunkStatus.FULL == this.cacheStatus[l]) { ++ ichunkaccess = this.cacheChunk[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 ++ ++ // move to first in cache ++ ++ for (int i1 = 3; i1 > 0; --i1) { ++ this.cachePos[i1] = this.cachePos[i1 - 1]; ++ this.cacheStatus[i1] = this.cacheStatus[i1 - 1]; ++ this.cacheChunk[i1] = this.cacheChunk[i1 - 1]; ++ } ++ ++ this.cachePos[0] = k; ++ this.cacheStatus[0] = ChunkStatus.FULL; ++ this.cacheChunk[0] = ichunkaccess; ++ ++ return CompletableFuture.completedFuture(Either.left(ichunkaccess)); ++ } ++ } ++ } ++ ++ if (gen) { ++ return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent); ++ } ++ ++ IChunkAccess current = this.getChunkAtImmediately(x, z); // we want to bypass ticket restrictions ++ if (current != null) { ++ if (!(current instanceof ProtoChunkExtension) && !(current instanceof Chunk)) { ++ return CompletableFuture.completedFuture(PlayerChunk.UNLOADED_CHUNK_ACCESS); ++ } ++ // we know the chunk is at full status here (either in read-only mode or the real thing) ++ return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent); ++ } ++ ++ ChunkStatus status = world.getChunkProvider().playerChunkMap.getStatusOnDiskNoLoad(x, z); ++ ++ if (status != null && status != ChunkStatus.FULL) { ++ // does not exist on disk ++ return CompletableFuture.completedFuture(PlayerChunk.UNLOADED_CHUNK_ACCESS); ++ } ++ ++ if (status == ChunkStatus.FULL) { ++ return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent); ++ } ++ ++ // status is null here ++ ++ // here we don't know what status it is and we're not supposed to generate ++ // so we asynchronously load empty status ++ return this.bringToStatusAsync(x, z, chunkPos, ChunkStatus.EMPTY, isUrgent).thenCompose((either) -> { ++ IChunkAccess chunk = either.left().orElse(null); ++ if (!(chunk instanceof ProtoChunkExtension) && !(chunk instanceof Chunk)) { ++ // the chunk on disk was not a full status chunk ++ return CompletableFuture.completedFuture(PlayerChunk.UNLOADED_CHUNK_ACCESS); ++ } ++ ; // bring to full status if required ++ return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent); ++ }); ++ } ++ ++ private CompletableFuture> bringToFullStatusAsync(int x, int z, ChunkCoordIntPair chunkPos, boolean isUrgent) { ++ return this.bringToStatusAsync(x, z, chunkPos, ChunkStatus.FULL, isUrgent); ++ } ++ ++ private CompletableFuture> bringToStatusAsync(int x, int z, ChunkCoordIntPair chunkPos, ChunkStatus status, boolean isUrgent) { ++ CompletableFuture> future = this.getChunkFutureMainThread(x, z, status, true, isUrgent); ++ Long identifier = Long.valueOf(this.asyncLoadSeqCounter++); ++ int ticketLevel = MCUtil.getTicketLevelFor(status); ++ this.addTicketAtLevel(TicketType.ASYNC_LOAD, chunkPos, ticketLevel, identifier); ++ ++ return future.thenComposeAsync((Either either) -> { ++ // either left -> success ++ // either right -> failure ++ ++ this.removeTicketAtLevel(TicketType.ASYNC_LOAD, chunkPos, ticketLevel, identifier); ++ this.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); // allow unloading ++ ++ Optional failure = either.right(); ++ ++ if (failure.isPresent()) { ++ // failure ++ throw new IllegalStateException("Chunk failed to load: " + failure.get().toString()); ++ } ++ ++ return CompletableFuture.completedFuture(either); ++ }, this.serverThreadQueue); ++ } ++ ++ public void addTicketAtLevel(TicketType ticketType, ChunkCoordIntPair chunkPos, int ticketLevel, T identifier) { ++ this.chunkMapDistance.addTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier); ++ } ++ ++ public void removeTicketAtLevel(TicketType ticketType, ChunkCoordIntPair chunkPos, int ticketLevel, T identifier) { ++ this.chunkMapDistance.removeTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier); ++ } + // Paper end + + @Nullable + @Override + public IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag) { ++ final int x = i; final int z = j; // Paper - conflict on variable change + if (Thread.currentThread() != this.serverThread) { + return (IChunkAccess) CompletableFuture.supplyAsync(() -> { + return this.getChunkAt(i, j, chunkstatus, flag); +@@ -359,11 +487,16 @@ public class ChunkProviderServer extends IChunkProvider { + } + + gameprofilerfiller.c("getChunkCacheMiss"); +- CompletableFuture> completablefuture = this.getChunkFutureMainThread(i, j, chunkstatus, flag); ++ CompletableFuture> completablefuture = this.getChunkFutureMainThread(i, j, chunkstatus, flag, true); // Paper + + if (!completablefuture.isDone()) { // Paper ++ // Paper start - async chunk io/loading ++ this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); ++ com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z); ++ // Paper end + this.world.timings.syncChunkLoad.startTiming(); // Paper + this.serverThreadQueue.awaitTasks(completablefuture::isDone); ++ com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug + this.world.timings.syncChunkLoad.stopTiming(); // Paper + } // Paper + ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { +@@ -429,6 +562,11 @@ public class ChunkProviderServer extends IChunkProvider { + } + + private CompletableFuture> getChunkFutureMainThread(int i, int j, ChunkStatus chunkstatus, boolean flag) { ++ // Paper start - add isUrgent - old sig left in place for dirty nms plugins ++ return getChunkFutureMainThread(i, j, chunkstatus, flag, false); ++ } ++ private CompletableFuture> getChunkFutureMainThread(int i, int j, ChunkStatus chunkstatus, boolean flag, boolean isUrgent) { ++ // Paper end + ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j); + long k = chunkcoordintpair.pair(); + int l = 33 + ChunkStatus.a(chunkstatus); +@@ -829,11 +967,12 @@ public class ChunkProviderServer extends IChunkProvider { + protected boolean executeNext() { + // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task + try { ++ boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask(); // Paper + if (ChunkProviderServer.this.tickDistanceManager()) { + return true; + } else { + ChunkProviderServer.this.lightEngine.queueUpdate(); +- return super.executeNext(); ++ return super.executeNext() || execChunkTask; // Paper + } + } finally { + playerChunkMap.callbackExecutor.run(); +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 75d4a8fc394449ccc006fe67a8842edcd9f36854..6433463938d8bb717840c8f57fe6e7079e1030f2 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -158,6 +158,18 @@ public class PlayerChunk { + } + return null; + } ++ ++ public ChunkStatus getChunkHolderStatus() { ++ for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getPreviousStatus(); curr != next; curr = next, next = next.getPreviousStatus()) { ++ CompletableFuture> future = this.getStatusFutureUnchecked(curr); ++ Either either = future.getNow(null); ++ if (either == null || !either.left().isPresent()) { ++ continue; ++ } ++ return curr; ++ } ++ return null; ++ } + // Paper end + + public CompletableFuture> getStatusFutureUnchecked(ChunkStatus chunkstatus) { +@@ -376,7 +388,7 @@ public class PlayerChunk { + ChunkStatus chunkstatus = getChunkStatus(this.oldTicketLevel); + ChunkStatus chunkstatus1 = getChunkStatus(this.ticketLevel); + boolean flag = this.oldTicketLevel <= PlayerChunkMap.GOLDEN_TICKET; +- boolean flag1 = this.ticketLevel <= PlayerChunkMap.GOLDEN_TICKET; ++ boolean flag1 = this.ticketLevel <= PlayerChunkMap.GOLDEN_TICKET; // Paper - diff on change: (flag1 = new ticket level is in loadable range) + PlayerChunk.State playerchunk_state = getChunkState(this.oldTicketLevel); + PlayerChunk.State playerchunk_state1 = getChunkState(this.ticketLevel); + // CraftBukkit start +@@ -412,6 +424,12 @@ public class PlayerChunk { + } + }); + ++ // Paper start ++ if (!flag1) { ++ playerchunkmap.world.asyncChunkTaskManager.cancelChunkLoad(this.location.x, this.location.z); ++ } ++ // Paper end ++ + for (int i = flag1 ? chunkstatus1.c() + 1 : 0; i <= chunkstatus.c(); ++i) { + completablefuture = (CompletableFuture) this.statusFutures.get(i); + if (completablefuture != null) { +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 71c2792d7eede35485cc36ac929cf295bcd4646b..a6c3bed5824d112042536a5666098d4d80173c3b 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -88,6 +88,7 @@ import net.minecraft.world.level.chunk.ProtoChunk; + import net.minecraft.world.level.chunk.ProtoChunkExtension; + import net.minecraft.world.level.chunk.storage.ChunkRegionLoader; + import net.minecraft.world.level.chunk.storage.IChunkLoader; ++import net.minecraft.world.level.chunk.storage.RegionFile; + import net.minecraft.world.level.levelgen.structure.StructureStart; + import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructureManager; + import net.minecraft.world.level.storage.Convertable; +@@ -112,7 +113,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + private final LightEngineThreaded lightEngine; + private final IAsyncTaskHandler executor; + public final ChunkGenerator chunkGenerator; +- private final Supplier l; ++ private final Supplier l; public final Supplier getWorldPersistentDataSupplier() { return this.l; } // Paper - OBFHELPER + private final VillagePlace m; + public final LongSet unloadQueue; + private boolean updatingChunksModified; +@@ -122,7 +123,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + public final WorldLoadListener worldLoadListener; + public final PlayerChunkMap.a chunkDistanceManager; + private final AtomicInteger u; +- private final DefinedStructureManager definedStructureManager; ++ public final DefinedStructureManager definedStructureManager; // Paper - private -> public + private final File w; + private final PlayerMap playerMap; + public final Int2ObjectMap trackedEntities; +@@ -205,7 +206,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.lightEngine = new LightEngineThreaded(ilightaccess, this, this.world.getDimensionManager().hasSkyLight(), threadedmailbox1, this.p.a(threadedmailbox1, false)); + this.chunkDistanceManager = new PlayerChunkMap.a(executor, iasynctaskhandler); + this.l = supplier; +- this.m = new VillagePlace(new File(this.w, "poi"), datafixer, flag); ++ this.m = new VillagePlace(new File(this.w, "poi"), datafixer, flag, this.world); // Paper + this.setViewDistance(i); + } + +@@ -247,12 +248,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + @Nullable +- protected PlayerChunk getUpdatingChunk(long i) { ++ public PlayerChunk getUpdatingChunk(long i) { // Paper + return (PlayerChunk) this.updatingChunks.get(i); + } + + @Nullable +- protected PlayerChunk getVisibleChunk(long i) { ++ public PlayerChunk getVisibleChunk(long i) { // Paper - protected -> public + return (PlayerChunk) this.visibleChunks.get(i); + } + +@@ -374,6 +375,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + public void close() throws IOException { + try { + this.p.close(); ++ this.world.asyncChunkTaskManager.close(true); // Paper - Required since we're closing regionfiles in the next line + this.m.close(); + } finally { + super.close(); +@@ -465,7 +467,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.b(() -> { + return true; + }); +- this.i(); ++ this.world.asyncChunkTaskManager.flush(); // Paper - flush to preserve behavior compat with pre-async behaviour ++// this.i(); // Paper - nuke IOWorker + PlayerChunkMap.LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.w.getName()); + } else { + this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).forEach((playerchunk) -> { +@@ -481,16 +484,20 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + } + +- private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.96; // Spigot ++ private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more + + protected void unloadChunks(BooleanSupplier booleansupplier) { + GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); + ++ try (Timing ignored = this.world.timings.poiUnload.startTiming()) { // Paper + gameprofilerfiller.enter("poi"); + this.m.a(booleansupplier); ++ } // Paper + gameprofilerfiller.exitEnter("chunk_unload"); + if (!this.world.isSavingDisabled()) { ++ try (Timing ignored = this.world.timings.chunkUnload.startTiming()) { // Paper + this.b(booleansupplier); ++ }// Paper + } + + gameprofilerfiller.exit(); +@@ -511,12 +518,13 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + if (playerchunk != null) { + this.pendingUnload.put(j, playerchunk); + this.updatingChunksModified = true; ++ this.a(j, playerchunk); // Paper - Move up - don't leak chunks + // Spigot start + if (!booleansupplier.getAsBoolean() && this.unloadQueue.size() <= targetSize && activityAccountant.activityTimeIsExhausted()) { + break; + } + // Spigot end +- this.a(j, playerchunk); ++ //this.a(j, playerchunk); // Paper - move up because spigot did a dumb + } + } + activityAccountant.endActivity(); // Spigot +@@ -530,6 +538,60 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + } + ++ // Paper start - async chunk save for unload ++ // Note: This is very unsafe to call if the chunk is still in use. ++ // This is also modeled after PlayerChunkMap#saveChunk(IChunkAccess, boolean), with the intentional difference being ++ // serializing the chunk is left to a worker thread. ++ private void asyncSave(IChunkAccess chunk) { ++ ChunkCoordIntPair chunkPos = chunk.getPos(); ++ NBTTagCompound poiData; ++ try (Timing ignored = this.world.timings.chunkUnloadPOISerialization.startTiming()) { ++ poiData = this.getVillagePlace().getData(chunk.getPos()); ++ } ++ ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, chunkPos.x, chunkPos.z, ++ poiData, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY); ++ ++ if (!chunk.isNeedsSaving()) { ++ return; ++ } ++ ++ ChunkStatus chunkstatus = chunk.getChunkStatus(); ++ ++ // Copied from PlayerChunkMap#saveChunk(IChunkAccess, boolean) ++ if (chunkstatus.getType() != ChunkStatus.Type.LEVELCHUNK) { ++ try (co.aikar.timings.Timing ignored1 = this.world.timings.chunkSaveOverwriteCheck.startTiming()) { // Paper ++ // Paper start - Optimize save by using status cache ++ try { ++ ChunkStatus statusOnDisk = this.getChunkStatusOnDisk(chunkPos); ++ if (statusOnDisk != null && statusOnDisk.getType() == ChunkStatus.Type.LEVELCHUNK) { ++ // Paper end ++ return; ++ } ++ ++ if (chunkstatus == ChunkStatus.EMPTY && chunk.h().values().stream().noneMatch(StructureStart::e)) { ++ return; ++ } ++ } catch (IOException ex) { ++ ex.printStackTrace(); ++ return; ++ } ++ } ++ } ++ ++ ChunkRegionLoader.AsyncSaveData asyncSaveData; ++ try (Timing ignored = this.world.timings.chunkUnloadPrepareSave.startTiming()) { ++ asyncSaveData = ChunkRegionLoader.getAsyncSaveData(this.world, chunk); ++ } ++ ++ this.world.asyncChunkTaskManager.scheduleChunkSave(chunkPos.x, chunkPos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY, ++ asyncSaveData, chunk); ++ ++ chunk.setLastSaved(this.world.getTime()); ++ chunk.setNeedsSaving(false); ++ } ++ // Paper end ++ + private void a(long i, PlayerChunk playerchunk) { + CompletableFuture completablefuture = playerchunk.getChunkSave(); + Consumer consumer = (ichunkaccess) -> { // CraftBukkit - decompile error +@@ -543,7 +605,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + ((Chunk) ichunkaccess).setLoaded(false); + } + +- this.saveChunk(ichunkaccess); ++ //this.saveChunk(ichunkaccess);// Paper - delay + if (this.loadedChunks.remove(i) && ichunkaccess instanceof Chunk) { + Chunk chunk = (Chunk) ichunkaccess; + +@@ -551,6 +613,13 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + this.autoSaveQueue.remove(playerchunk); // Paper + ++ try { ++ this.asyncSave(ichunkaccess); // Paper - async chunk saving ++ } catch (Throwable ex) { ++ LOGGER.fatal("Failed to prepare async save, attempting synchronous save", ex); ++ this.saveChunk(ichunkaccess); ++ } ++ + this.lightEngine.a(ichunkaccess.getPos()); + this.lightEngine.queueUpdate(); + this.worldLoadListener.a(ichunkaccess.getPos(), (ChunkStatus) null); +@@ -621,19 +690,23 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + private CompletableFuture> f(ChunkCoordIntPair chunkcoordintpair) { +- return CompletableFuture.supplyAsync(() -> { ++ // Paper start - Async chunk io ++ final java.util.function.BiFunction> syncLoadComplete = (chunkHolder, ioThrowable) -> { + try (Timing ignored = this.world.timings.chunkLoad.startTimingIfSync()) { // Paper + this.world.getMethodProfiler().c("chunkLoad"); +- NBTTagCompound nbttagcompound; // Paper +- try (Timing ignored2 = this.world.timings.chunkIO.startTimingIfSync()) { // Paper start - timings +- nbttagcompound = this.readChunkData(chunkcoordintpair); +- } // Paper end ++ // Paper start ++ if (ioThrowable != null) { ++ com.destroystokyo.paper.util.SneakyThrow.sneaky(ioThrowable); ++ } + +- if (nbttagcompound != null) {try (Timing ignored2 = this.world.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings +- boolean flag = nbttagcompound.hasKeyOfType("Level", 10) && nbttagcompound.getCompound("Level").hasKeyOfType("Status", 8); ++ this.getVillagePlace().loadInData(chunkcoordintpair, chunkHolder.poiData); ++ chunkHolder.tasks.forEach(Runnable::run); ++ // Paper end + +- if (flag) { +- ProtoChunk protochunk = ChunkRegionLoader.loadChunk(this.world, this.definedStructureManager, this.m, chunkcoordintpair, nbttagcompound); ++ if (chunkHolder.protoChunk != null) {try (Timing ignored2 = this.world.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings // Paper - chunk is created async ++ ++ if (true) { ++ ProtoChunk protochunk = chunkHolder.protoChunk; + + protochunk.setLastSaved(this.world.getTime()); + this.a(chunkcoordintpair, protochunk.getChunkStatus().getType()); +@@ -657,7 +730,32 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + this.g(chunkcoordintpair); + return Either.left(new ProtoChunk(chunkcoordintpair, ChunkConverter.a, this.world)); // Paper - Anti-Xray - Add parameter +- }, this.executor); ++ // Paper start - Async chunk io ++ }; ++ CompletableFuture> ret = new CompletableFuture<>(); ++ ++ Consumer chunkHolderConsumer = (ChunkRegionLoader.InProgressChunkHolder holder) -> { ++ // Go into the chunk load queue and not server task queue so we can be popped out even faster. ++ com.destroystokyo.paper.io.chunk.ChunkTaskManager.queueChunkWaitTask(() -> { ++ try { ++ ret.complete(syncLoadComplete.apply(holder, null)); ++ } catch (Exception e) { ++ ret.completeExceptionally(e); ++ } ++ }); ++ }; ++ ++ CompletableFuture chunkSaveFuture = this.world.asyncChunkTaskManager.getChunkSaveFuture(chunkcoordintpair.x, chunkcoordintpair.z); ++ if (chunkSaveFuture != null) { ++ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, ++ com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture); ++ this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY); ++ } else { ++ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, ++ com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false); ++ } ++ return ret; ++ // Paper end + } + + private void g(ChunkCoordIntPair chunkcoordintpair) { +@@ -884,6 +982,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + public boolean saveChunk(IChunkAccess ichunkaccess) { ++ try (co.aikar.timings.Timing ignored = this.world.timings.chunkSave.startTiming()) { // Paper + this.m.a(ichunkaccess.getPos()); + if (!ichunkaccess.isNeedsSaving()) { + return false; +@@ -896,6 +995,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + ChunkStatus chunkstatus = ichunkaccess.getChunkStatus(); + + if (chunkstatus.getType() != ChunkStatus.Type.LEVELCHUNK) { ++ try (co.aikar.timings.Timing ignored1 = this.world.timings.chunkSaveOverwriteCheck.startTiming()) { // Paper + if (this.h(chunkcoordintpair)) { + return false; + } +@@ -903,12 +1003,20 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + if (chunkstatus == ChunkStatus.EMPTY && ichunkaccess.h().values().stream().noneMatch(StructureStart::e)) { + return false; + } ++ } // Paper + } + + this.world.getMethodProfiler().c("chunkSave"); +- NBTTagCompound nbttagcompound = ChunkRegionLoader.saveChunk(this.world, ichunkaccess); ++ NBTTagCompound nbttagcompound; ++ try (co.aikar.timings.Timing ignored1 = this.world.timings.chunkSaveDataSerialization.startTiming()) { // Paper ++ nbttagcompound = ChunkRegionLoader.saveChunk(this.world, ichunkaccess); ++ } // Paper ++ + +- this.a(chunkcoordintpair, nbttagcompound); ++ // Paper start - async chunk io ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, chunkcoordintpair.x, chunkcoordintpair.z, ++ null, nbttagcompound, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); ++ // Paper end - async chunk io + this.a(chunkcoordintpair, chunkstatus.getType()); + return true; + } catch (Exception exception) { +@@ -917,6 +1025,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return false; + } + } ++ } // Paper + } + + private boolean h(ChunkCoordIntPair chunkcoordintpair) { +@@ -1046,6 +1155,35 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + } + ++ // Paper start - Asynchronous chunk io ++ @Nullable ++ @Override ++ public NBTTagCompound read(ChunkCoordIntPair chunkcoordintpair) throws IOException { ++ if (Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { ++ NBTTagCompound ret = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE ++ .loadChunkDataAsyncFuture(this.world, chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(), ++ false, true, true).join().chunkData; ++ ++ if (ret == com.destroystokyo.paper.io.PaperFileIOThread.FAILURE_VALUE) { ++ throw new IOException("See logs for further detail"); ++ } ++ return ret; ++ } ++ return super.read(chunkcoordintpair); ++ } ++ ++ @Override ++ public void write(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws IOException { ++ if (Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave( ++ this.world, chunkcoordintpair.x, chunkcoordintpair.z, null, nbttagcompound, ++ com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread()); ++ return; ++ } ++ super.write(chunkcoordintpair, nbttagcompound); ++ } ++ // Paper end ++ + @Nullable + public NBTTagCompound readChunkData(ChunkCoordIntPair chunkcoordintpair) throws IOException { // Paper - private -> public + NBTTagCompound nbttagcompound = this.read(chunkcoordintpair); +@@ -1067,33 +1205,55 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + // Paper start - chunk status cache "api" + public ChunkStatus getChunkStatusOnDiskIfCached(ChunkCoordIntPair chunkPos) { +- RegionFile regionFile = this.getIOWorker().getRegionFileCache().getRegionFileIfLoaded(chunkPos); ++ synchronized (this) { // Paper ++ RegionFile regionFile = this.regionFileCache.getRegionFileIfLoaded(chunkPos); + + return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ } // Paper + } + + public ChunkStatus getChunkStatusOnDisk(ChunkCoordIntPair chunkPos) throws IOException { +- RegionFile regionFile = this.getIOWorker().getRegionFileCache().getFile(chunkPos, true); ++ // Paper start - async chunk save for unload ++ IChunkAccess unloadingChunk = this.world.asyncChunkTaskManager.getChunkInSaveProgress(chunkPos.x, chunkPos.z); ++ if (unloadingChunk != null) { ++ return unloadingChunk.getChunkStatus(); ++ } ++ // Paper end ++ // Paper start - async io ++ NBTTagCompound inProgressWrite = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE ++ .getPendingWrite(this.world, chunkPos.x, chunkPos.z, false); + +- if (regionFile == null || !regionFile.chunkExists(chunkPos)) { +- return null; ++ if (inProgressWrite != null) { ++ return ChunkRegionLoader.getStatus(inProgressWrite); + } ++ // Paper end ++ synchronized (this) { // Paper - async io ++ RegionFile regionFile = this.regionFileCache.getFile(chunkPos, true); ++ ++ if (regionFile == null || !regionFile.chunkExists(chunkPos)) { ++ return null; ++ } + +- ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); + +- if (status != null) { +- return status; ++ if (status != null) { ++ return status; ++ } ++ // Paper start - async io + } + +- this.readChunkData(chunkPos); ++ NBTTagCompound compound = this.readChunkData(chunkPos); + +- return regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ return ChunkRegionLoader.getStatus(compound); ++ // Paper end + } + + public void updateChunkStatusOnDisk(ChunkCoordIntPair chunkPos, @Nullable NBTTagCompound compound) throws IOException { +- RegionFile regionFile = this.getIOWorker().getRegionFileCache().getFile(chunkPos, false); ++ synchronized (this) { ++ RegionFile regionFile = this.regionFileCache.getFile(chunkPos, false); + +- regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkRegionLoader.getStatus(compound)); ++ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkRegionLoader.getStatus(compound)); ++ } + } + + public IChunkAccess getUnloadingChunk(int chunkX, int chunkZ) { +@@ -1102,6 +1262,39 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + // Paper end + ++ ++ // Paper start - async io ++ // this function will not load chunk data off disk to check for status ++ // ret null for unknown, empty for empty status on disk or absent from disk ++ public ChunkStatus getStatusOnDiskNoLoad(int x, int z) { ++ // Paper start - async chunk save for unload ++ IChunkAccess unloadingChunk = this.world.asyncChunkTaskManager.getChunkInSaveProgress(x, z); ++ if (unloadingChunk != null) { ++ return unloadingChunk.getChunkStatus(); ++ } ++ // Paper end ++ // Paper start - async io ++ NBTTagCompound inProgressWrite = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE ++ .getPendingWrite(this.world, x, z, false); ++ ++ if (inProgressWrite != null) { ++ return ChunkRegionLoader.getStatus(inProgressWrite); ++ } ++ // Paper end ++ // variant of PlayerChunkMap#getChunkStatusOnDisk that does not load data off disk, but loads the region file ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(x, z); ++ synchronized (world.getChunkProvider().playerChunkMap) { ++ RegionFile file; ++ try { ++ file = world.getChunkProvider().playerChunkMap.regionFileCache.getFile(chunkPos, false); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ return !file.chunkExists(chunkPos) ? ChunkStatus.EMPTY : file.getStatusIfCached(x, z); ++ } ++ } ++ + boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair) { + // Spigot start + return isOutsideOfRange(chunkcoordintpair, false); +@@ -1448,6 +1641,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + } + ++ public VillagePlace getVillagePlace() { return this.h(); } // Paper - OBFHELPER + protected VillagePlace h() { + return this.m; + } +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 285a03b57431bd6a4d26bb84e916d2c6e1eb0213..218dc900e125a11548485887b1918742072c7a77 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 PLUGIN = a("plugin", (a, b) -> 0); // CraftBukkit + public static final TicketType PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType FUTURE_AWAIT = a("future_await", Long::compareTo); // Paper ++ public static final TicketType ASYNC_LOAD = a("async_load", Long::compareTo); // Paper + + public static TicketType a(String s, Comparator comparator) { + return new TicketType<>(s, comparator, 0L); +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 1b05d2394244d85a63ecd8336f7dd1d05f4fdffe..a68be9b72daca6353d51141a8771e00240375315 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -51,6 +51,7 @@ import net.minecraft.core.IRegistry; + import net.minecraft.core.IRegistryCustom; + import net.minecraft.core.SectionPosition; + import net.minecraft.core.particles.ParticleParam; ++import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.chat.ChatMessage; + import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.network.protocol.Packet; +@@ -126,6 +127,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.chunk.ChunkSection; + import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.IChunkAccess; ++import net.minecraft.world.level.chunk.storage.RegionFile; + import net.minecraft.world.level.dimension.DimensionManager; + import net.minecraft.world.level.dimension.end.EnderDragonBattle; + import net.minecraft.world.level.levelgen.HeightMap; +@@ -208,6 +210,79 @@ public class WorldServer extends World implements GeneratorAccessSeed { + return this.chunkProvider.getChunkAt(x, z, false); + } + ++ // Paper start - Asynchronous IO ++ public final com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController poiDataController = new com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController() { ++ @Override ++ public void writeData(int x, int z, NBTTagCompound compound) throws java.io.IOException { ++ WorldServer.this.getChunkProvider().playerChunkMap.getVillagePlace().write(new ChunkCoordIntPair(x, z), compound); ++ } ++ ++ @Override ++ public NBTTagCompound readData(int x, int z) throws java.io.IOException { ++ return WorldServer.this.getChunkProvider().playerChunkMap.getVillagePlace().read(new ChunkCoordIntPair(x, z)); ++ } ++ ++ @Override ++ public T computeForRegionFile(int chunkX, int chunkZ, java.util.function.Function function) { ++ synchronized (WorldServer.this.getChunkProvider().playerChunkMap.getVillagePlace()) { ++ RegionFile file; ++ ++ try { ++ file = WorldServer.this.getChunkProvider().playerChunkMap.getVillagePlace().getFile(new ChunkCoordIntPair(chunkX, chunkZ), false); ++ } catch (java.io.IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ return function.apply(file); ++ } ++ } ++ ++ @Override ++ public T computeForRegionFileIfLoaded(int chunkX, int chunkZ, java.util.function.Function function) { ++ synchronized (WorldServer.this.getChunkProvider().playerChunkMap.getVillagePlace()) { ++ RegionFile file = WorldServer.this.getChunkProvider().playerChunkMap.getVillagePlace().getRegionFileIfLoaded(new ChunkCoordIntPair(chunkX, chunkZ)); ++ return function.apply(file); ++ } ++ } ++ }; ++ ++ public final com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController chunkDataController = new com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController() { ++ @Override ++ public void writeData(int x, int z, NBTTagCompound compound) throws java.io.IOException { ++ WorldServer.this.getChunkProvider().playerChunkMap.write(new ChunkCoordIntPair(x, z), compound); ++ } ++ ++ @Override ++ public NBTTagCompound readData(int x, int z) throws java.io.IOException { ++ return WorldServer.this.getChunkProvider().playerChunkMap.read(new ChunkCoordIntPair(x, z)); ++ } ++ ++ @Override ++ public T computeForRegionFile(int chunkX, int chunkZ, java.util.function.Function function) { ++ synchronized (WorldServer.this.getChunkProvider().playerChunkMap) { ++ RegionFile file; ++ ++ try { ++ file = WorldServer.this.getChunkProvider().playerChunkMap.regionFileCache.getFile(new ChunkCoordIntPair(chunkX, chunkZ), false); ++ } catch (java.io.IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ return function.apply(file); ++ } ++ } ++ ++ @Override ++ public T computeForRegionFileIfLoaded(int chunkX, int chunkZ, java.util.function.Function function) { ++ synchronized (WorldServer.this.getChunkProvider().playerChunkMap) { ++ RegionFile file = WorldServer.this.getChunkProvider().playerChunkMap.regionFileCache.getRegionFileIfLoaded(new ChunkCoordIntPair(chunkX, chunkZ)); ++ return function.apply(file); ++ } ++ } ++ }; ++ public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager; ++ // Paper end ++ + // Add env and gen to constructor, WorldData -> WorldDataServer + public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { + super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor +@@ -255,6 +330,8 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.dragonBattle = null; + } + this.getServer().addWorld(this.getWorld()); // CraftBukkit ++ ++ this.asyncChunkTaskManager = new com.destroystokyo.paper.io.chunk.ChunkTaskManager(this); // Paper + } + + // CraftBukkit start +@@ -1744,7 +1821,10 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + MCUtil.getSpiralOutChunks(spawn, radiusInBlocks >> 4).forEach(pair -> { +- getChunkProvider().getChunkAtMainThread(pair.x, pair.z); ++ getChunkProvider().getChunkAtAsynchronously(pair.x, pair.z, true, false).exceptionally((ex) -> { ++ ex.printStackTrace(); ++ return null; ++ }); + }); + } + public void removeTicketsForSpawn(int radiusInBlocks, BlockPosition spawn) { +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index d84d87b0dd01b4d67e55f1b445928285c45a08ca..fa0b7edf42243d53d9dc897903b9b9e902b33cf7 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -730,6 +730,13 @@ public class PlayerConnection implements PacketListenerPlayIn { + minecraftServer.scheduleOnMain(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0]))); // Paper + return; + } ++ // Paper start ++ String str = packetplayintabcomplete.c(); int index = -1; ++ if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) { ++ minecraftServer.scheduleOnMain(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0]))); // Paper ++ return; ++ } ++ // Paper end + // CraftBukkit end + StringReader stringreader = new StringReader(packetplayintabcomplete.c()); + +diff --git a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +index e5641f2b41d89a57285fc072a48b951aa03a14a7..ca23ca14d8011fc8daa7e20f2eaa550a8ff92c53 100644 +--- a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java ++++ b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +@@ -91,7 +91,7 @@ public abstract class IAsyncTaskHandler implements Mailbox public + while (this.executeNext()) { + ; + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java +index aa1d948e6aebef25f0f4c4c07f5131d2e8387e59..04b01cb841dc4f34ded5aaa4ea7a8e6d4b470183 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java +@@ -22,7 +22,9 @@ import java.util.stream.Stream; + import net.minecraft.SystemUtils; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.SectionPosition; ++import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.server.level.LightEngineGraphSection; ++import net.minecraft.server.level.WorldServer; + import net.minecraft.util.datafix.DataFixTypes; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.IWorldReader; +@@ -36,8 +38,16 @@ public class VillagePlace extends RegionFileSection { + private final VillagePlace.a a = new VillagePlace.a(); + private final LongSet b = new LongOpenHashSet(); + ++ private final WorldServer world; // Paper ++ + public VillagePlace(File file, DataFixer datafixer, boolean flag) { ++ // Paper start - add world parameter ++ this(file, datafixer, flag, null); ++ } ++ public VillagePlace(File file, DataFixer datafixer, boolean flag, WorldServer world) { + super(file, VillagePlaceSection::a, VillagePlaceSection::new, datafixer, DataFixTypes.POI_CHUNK, flag); ++ this.world = world; ++ // Paper end - add world parameter + } + + public void a(BlockPosition blockposition, VillagePlaceType villageplacetype) { +@@ -155,7 +165,23 @@ public class VillagePlace extends RegionFileSection { + + @Override + public void a(BooleanSupplier booleansupplier) { +- super.a(booleansupplier); ++ // Paper start - async chunk io ++ if (this.world == null) { ++ super.a(booleansupplier); ++ } else { ++ //super.a(booleansupplier); // re-implement below ++ while (!((RegionFileSection)this).d.isEmpty() && booleansupplier.getAsBoolean()) { ++ ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(((RegionFileSection)this).d.firstLong()).r(); ++ ++ NBTTagCompound data; ++ try (co.aikar.timings.Timing ignored1 = this.world.timings.poiSaveDataSerialization.startTiming()) { ++ data = this.getData(chunkcoordintpair); ++ } ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, ++ chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY); ++ } ++ } ++ // Paper end + this.a.a(); + } + +@@ -255,6 +281,35 @@ public class VillagePlace extends RegionFileSection { + } + } + ++ // Paper start - Asynchronous chunk io ++ @javax.annotation.Nullable ++ @Override ++ public NBTTagCompound read(ChunkCoordIntPair chunkcoordintpair) throws java.io.IOException { ++ if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { ++ NBTTagCompound ret = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE ++ .loadChunkDataAsyncFuture(this.world, chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(), ++ true, false, true).join().poiData; ++ ++ if (ret == com.destroystokyo.paper.io.PaperFileIOThread.FAILURE_VALUE) { ++ throw new java.io.IOException("See logs for further detail"); ++ } ++ return ret; ++ } ++ return super.read(chunkcoordintpair); ++ } ++ ++ @Override ++ public void write(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws java.io.IOException { ++ if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave( ++ this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, null, ++ com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread()); ++ return; ++ } ++ super.write(chunkcoordintpair, nbttagcompound); ++ } ++ // Paper end ++ + public static enum Occupancy { + + HAS_SPACE(VillagePlaceRecord::d), IS_OCCUPIED(VillagePlaceRecord::e), ANY((villageplacerecord) -> { +diff --git a/src/main/java/net/minecraft/world/level/NextTickListEntry.java b/src/main/java/net/minecraft/world/level/NextTickListEntry.java +index f3bcb96232d18abbcd86b079a7c5830bb30d75d2..37b7dd82a227a88b720c13a813dd7e8caf803e03 100644 +--- a/src/main/java/net/minecraft/world/level/NextTickListEntry.java ++++ b/src/main/java/net/minecraft/world/level/NextTickListEntry.java +@@ -5,7 +5,7 @@ import net.minecraft.core.BlockPosition; + + public class NextTickListEntry { + +- private static long d; ++ private static final java.util.concurrent.atomic.AtomicLong COUNTER = new java.util.concurrent.atomic.AtomicLong(); // Paper - async chunk loading + private final T e; + public final BlockPosition a; + public final long b; +@@ -17,7 +17,7 @@ public class NextTickListEntry { + } + + public NextTickListEntry(BlockPosition blockposition, T t0, long i, TickListPriority ticklistpriority) { +- this.f = (long) (NextTickListEntry.d++); ++ this.f = (long) (NextTickListEntry.COUNTER.getAndIncrement()); // Paper - async chunk loading + this.a = blockposition.immutableCopy(); + this.e = t0; + this.b = i; +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 711308cf84a816f09d116a7414f9cbee803c8713..f094ddf6b4d155f3c7a08a3b811c98b0862fd098 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +@@ -170,6 +170,7 @@ public class ChunkStatus { + return ChunkStatus.q.size(); + } + ++ public static int getTicketLevelOffset(ChunkStatus status) { return ChunkStatus.a(status); } // Paper - OBFHELPER + public static int a(ChunkStatus chunkstatus) { + return ChunkStatus.r.getInt(chunkstatus.c()); + } +@@ -185,6 +186,7 @@ public class ChunkStatus { + this.t = chunkstatus == null ? 0 : chunkstatus.c() + 1; + } + ++ public final int getStatusIndex() { return c(); } // Paper - OBFHELPER + public int c() { + return this.t; + } +@@ -193,7 +195,7 @@ public class ChunkStatus { + return this.s; + } + +- public ChunkStatus getPreviousStatus() { return this.e(); } // Paper - OBFHELPER ++ public final ChunkStatus getPreviousStatus() { return this.e(); } // Paper - OBFHELPER + public ChunkStatus e() { + return this.u; + } +@@ -206,6 +208,7 @@ public class ChunkStatus { + return this.w.doWork(this, worldserver, definedstructuremanager, lightenginethreaded, function, ichunkaccess); + } + ++ public final int getNeighborRadius() { return this.f(); } // Paper - OBFHELPER + public int f() { + return this.x; + } +@@ -233,6 +236,7 @@ public class ChunkStatus { + return this.z; + } + ++ public final boolean isAtLeastStatus(ChunkStatus chunkstatus) { return b(chunkstatus); } // Paper - OBFHELPER + public boolean b(ChunkStatus chunkstatus) { + return this.c() >= chunkstatus.c(); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java b/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java +index a2518fdadd1d7239e8614f498a5223144f1c2a36..86b4db483787c5fd10461f7d7e90a772ee049599 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java ++++ b/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java +@@ -73,6 +73,7 @@ public class NibbleArray { + return this.a; + } + ++ public NibbleArray copy() { return this.b(); } // Paper - OBFHELPER + public NibbleArray b() { + return this.a == null ? new NibbleArray() : new NibbleArray((byte[]) this.a.clone()); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +index c652897aae99c48c6cc020b5d64f6a8b02beecb5..c95fcdf47db8bfe59a83c0d28f4744b4d8540ef8 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +@@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + import it.unimi.dsi.fastutil.longs.LongSet; + import it.unimi.dsi.fastutil.shorts.ShortList; + import it.unimi.dsi.fastutil.shorts.ShortListIterator; ++import java.util.ArrayDeque; // Paper + import java.util.Arrays; + import java.util.BitSet; + import java.util.EnumSet; +@@ -66,7 +67,29 @@ public class ChunkRegionLoader { + + private static final Logger LOGGER = LogManager.getLogger(); + ++ // Paper start ++ public static final class InProgressChunkHolder { ++ ++ public final ProtoChunk protoChunk; ++ public final ArrayDeque tasks; ++ ++ public NBTTagCompound poiData; ++ ++ public InProgressChunkHolder(final ProtoChunk protoChunk, final ArrayDeque tasks) { ++ this.protoChunk = protoChunk; ++ this.tasks = tasks; ++ } ++ } ++ + public static ProtoChunk loadChunk(WorldServer worldserver, DefinedStructureManager definedstructuremanager, VillagePlace villageplace, ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) { ++ InProgressChunkHolder holder = loadChunk(worldserver, definedstructuremanager, villageplace, chunkcoordintpair, nbttagcompound, true); ++ holder.tasks.forEach(Runnable::run); ++ return holder.protoChunk; ++ } ++ ++ public static InProgressChunkHolder loadChunk(WorldServer worldserver, DefinedStructureManager definedstructuremanager, VillagePlace villageplace, ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound, boolean distinguish) { ++ ArrayDeque tasksToExecuteOnMain = new ArrayDeque<>(); ++ // Paper end + ChunkGenerator chunkgenerator = worldserver.getChunkProvider().getChunkGenerator(); + WorldChunkManager worldchunkmanager = chunkgenerator.getWorldChunkManager(); + NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level"); +@@ -93,7 +116,9 @@ public class ChunkRegionLoader { + LightEngine lightengine = chunkproviderserver.getLightEngine(); + + if (flag) { +- lightengine.b(chunkcoordintpair, true); ++ tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main ++ lightengine.b(chunkcoordintpair, true); ++ }); // Paper - delay this task since we're executing off-main + } + + for (int i = 0; i < nbttaglist.size(); ++i) { +@@ -109,16 +134,28 @@ public class ChunkRegionLoader { + achunksection[b0] = chunksection; + } + +- villageplace.a(chunkcoordintpair, chunksection); ++ tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main ++ villageplace.a(chunkcoordintpair, chunksection); ++ }); // Paper - delay this task since we're executing off-main + } + + if (flag) { + if (nbttagcompound2.hasKeyOfType("BlockLight", 7)) { +- lightengine.a(EnumSkyBlock.BLOCK, SectionPosition.a(chunkcoordintpair, b0), new NibbleArray(nbttagcompound2.getByteArray("BlockLight")), true); ++ // Paper start - delay this task since we're executing off-main ++ NibbleArray blockLight = new NibbleArray(nbttagcompound2.getByteArray("BlockLight")); ++ tasksToExecuteOnMain.add(() -> { ++ lightengine.a(EnumSkyBlock.BLOCK, SectionPosition.a(chunkcoordintpair, b0), blockLight, true); ++ }); ++ // Paper end - delay this task since we're executing off-main + } + + if (flag2 && nbttagcompound2.hasKeyOfType("SkyLight", 7)) { +- lightengine.a(EnumSkyBlock.SKY, SectionPosition.a(chunkcoordintpair, b0), new NibbleArray(nbttagcompound2.getByteArray("SkyLight")), true); ++ // Paper start - delay this task since we're executing off-main ++ NibbleArray skyLight = new NibbleArray(nbttagcompound2.getByteArray("SkyLight")); ++ tasksToExecuteOnMain.add(() -> { ++ lightengine.a(EnumSkyBlock.SKY, SectionPosition.a(chunkcoordintpair, b0), skyLight, true); ++ }); ++ // Paper end - delay this task since we're executing off-main + } + } + } +@@ -227,7 +264,7 @@ public class ChunkRegionLoader { + } + + if (chunkstatus_type == ChunkStatus.Type.LEVELCHUNK) { +- return new ProtoChunkExtension((Chunk) object); ++ return new InProgressChunkHolder(new ProtoChunkExtension((Chunk) object), tasksToExecuteOnMain); // Paper - Async chunk loading + } else { + ProtoChunk protochunk1 = (ProtoChunk) object; + +@@ -266,11 +303,83 @@ public class ChunkRegionLoader { + protochunk1.a(worldgenstage_features, BitSet.valueOf(nbttagcompound5.getByteArray(s1))); + } + +- return protochunk1; ++ return new InProgressChunkHolder(protochunk1, tasksToExecuteOnMain); // Paper - Async chunk loading ++ } ++ } ++ ++ // Paper start - async chunk save for unload ++ public static final class AsyncSaveData { ++ public final NibbleArray[] blockLight; // null or size of 17 (for indices -1 through 15) ++ public final NibbleArray[] skyLight; ++ ++ public final NBTTagList blockTickList; // non-null if we had to go to the server's tick list ++ public final NBTTagList fluidTickList; // non-null if we had to go to the server's tick list ++ ++ public final long worldTime; ++ ++ public AsyncSaveData(NibbleArray[] blockLight, NibbleArray[] skyLight, NBTTagList blockTickList, NBTTagList fluidTickList, ++ long worldTime) { ++ this.blockLight = blockLight; ++ this.skyLight = skyLight; ++ this.blockTickList = blockTickList; ++ this.fluidTickList = fluidTickList; ++ this.worldTime = worldTime; + } + } + ++ // must be called sync ++ public static AsyncSaveData getAsyncSaveData(WorldServer world, IChunkAccess chunk) { ++ org.spigotmc.AsyncCatcher.catchOp("preparation of chunk data for async save"); ++ ChunkCoordIntPair chunkPos = chunk.getPos(); ++ ++ LightEngineThreaded lightenginethreaded = world.getChunkProvider().getLightEngine(); ++ ++ NibbleArray[] blockLight = new NibbleArray[17 - (-1)]; ++ NibbleArray[] skyLight = new NibbleArray[17 - (-1)]; ++ ++ for (int i = -1; i < 17; ++i) { ++ NibbleArray blockArray = lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkPos, i)); ++ NibbleArray skyArray = lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkPos, i)); ++ ++ // copy data for safety ++ if (blockArray != null) { ++ blockArray = blockArray.copy(); ++ } ++ if (skyArray != null) { ++ skyArray = skyArray.copy(); ++ } ++ ++ // apply offset of 1 for -1 starting index ++ blockLight[i + 1] = blockArray; ++ skyLight[i + 1] = skyArray; ++ } ++ ++ TickList blockTickList = chunk.n(); ++ ++ NBTTagList blockTickListSerialized; ++ if (blockTickList instanceof ProtoChunkTickList || blockTickList instanceof TickListChunk) { ++ blockTickListSerialized = null; ++ } else { ++ blockTickListSerialized = world.getBlockTickList().a(chunkPos); ++ } ++ ++ TickList fluidTickList = chunk.o(); ++ ++ NBTTagList fluidTickListSerialized; ++ if (fluidTickList instanceof ProtoChunkTickList || fluidTickList instanceof TickListChunk) { ++ fluidTickListSerialized = null; ++ } else { ++ fluidTickListSerialized = world.getFluidTickList().a(chunkPos); ++ } ++ ++ return new AsyncSaveData(blockLight, skyLight, blockTickListSerialized, fluidTickListSerialized, world.getTime()); ++ } ++ + public static NBTTagCompound saveChunk(WorldServer worldserver, IChunkAccess ichunkaccess) { ++ return saveChunk(worldserver, ichunkaccess, null); ++ } ++ public static NBTTagCompound saveChunk(WorldServer worldserver, IChunkAccess ichunkaccess, AsyncSaveData asyncsavedata) { ++ // Paper end + ChunkCoordIntPair chunkcoordintpair = ichunkaccess.getPos(); + NBTTagCompound nbttagcompound = new NBTTagCompound(); + NBTTagCompound nbttagcompound1 = new NBTTagCompound(); +@@ -279,7 +388,7 @@ public class ChunkRegionLoader { + nbttagcompound.set("Level", nbttagcompound1); + nbttagcompound1.setInt("xPos", chunkcoordintpair.x); + nbttagcompound1.setInt("zPos", chunkcoordintpair.z); +- nbttagcompound1.setLong("LastUpdate", worldserver.getTime()); ++ nbttagcompound1.setLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : worldserver.getTime()); // Paper - async chunk unloading + nbttagcompound1.setLong("InhabitedTime", ichunkaccess.getInhabitedTime()); + nbttagcompound1.setString("Status", ichunkaccess.getChunkStatus().d()); + ChunkConverter chunkconverter = ichunkaccess.p(); +@@ -295,14 +404,22 @@ public class ChunkRegionLoader { + + NBTTagCompound nbttagcompound2; + +- for (int i = -1; i < 17; ++i) { ++ for (int i = -1; i < 17; ++i) { // Paper - conflict on loop parameter change + int finalI = i; // CraftBukkit - decompile errors + ChunkSection chunksection = (ChunkSection) Arrays.stream(achunksection).filter((chunksection1) -> { + return chunksection1 != null && chunksection1.getYPosition() >> 4 == finalI; // CraftBukkit - decompile errors + }).findFirst().orElse(Chunk.a); +- NibbleArray nibblearray = lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, i)); +- NibbleArray nibblearray1 = lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, i)); +- ++ // Paper start - async chunk save for unload ++ NibbleArray nibblearray; // block light ++ NibbleArray nibblearray1; // sky light ++ if (asyncsavedata == null) { ++ nibblearray = lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, i)); /// Paper - diff on method change (see getAsyncSaveData) ++ nibblearray1 = lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, i)); // Paper - diff on method change (see getAsyncSaveData) ++ } else { ++ nibblearray = asyncsavedata.blockLight[i + 1]; // +1 to offset the -1 starting index ++ nibblearray1 = asyncsavedata.skyLight[i + 1]; // +1 to offset the -1 starting index ++ } ++ // Paper end + if (chunksection != Chunk.a || nibblearray != null || nibblearray1 != null) { + nbttagcompound2 = new NBTTagCompound(); + nbttagcompound2.setByte("Y", (byte) (i & 255)); +@@ -369,7 +486,7 @@ public class ChunkRegionLoader { + Entity entity = (Entity) iterator1.next(); + NBTTagCompound nbttagcompound4 = new NBTTagCompound(); + // Paper start +- if ((int) Math.floor(entity.locX()) >> 4 != chunk.getPos().x || (int) Math.floor(entity.locZ()) >> 4 != chunk.getPos().z) { ++ if (asyncsavedata == null && !entity.dead && (int) Math.floor(entity.locX()) >> 4 != chunk.getPos().x || (int) Math.floor(entity.locZ()) >> 4 != chunk.getPos().z) { + toUpdate.add(entity); + continue; + } +@@ -412,24 +529,32 @@ public class ChunkRegionLoader { + } + + nbttagcompound1.set("Entities", nbttaglist2); +- TickList ticklist = ichunkaccess.n(); ++ TickList ticklist = ichunkaccess.n(); // Paper - diff on method change (see getAsyncSaveData) + + if (ticklist instanceof ProtoChunkTickList) { + nbttagcompound1.set("ToBeTicked", ((ProtoChunkTickList) ticklist).b()); + } else if (ticklist instanceof TickListChunk) { + nbttagcompound1.set("TileTicks", ((TickListChunk) ticklist).b()); ++ // Paper start - async chunk save for unload ++ } else if (asyncsavedata != null) { ++ nbttagcompound1.set("TileTicks", asyncsavedata.blockTickList); ++ // Paper end + } else { +- nbttagcompound1.set("TileTicks", worldserver.getBlockTickList().a(chunkcoordintpair)); ++ nbttagcompound1.set("TileTicks", worldserver.getBlockTickList().a(chunkcoordintpair)); // Paper - diff on method change (see getAsyncSaveData) + } + +- TickList ticklist1 = ichunkaccess.o(); ++ TickList ticklist1 = ichunkaccess.o(); // Paper - diff on method change (see getAsyncSaveData) + + if (ticklist1 instanceof ProtoChunkTickList) { + nbttagcompound1.set("LiquidsToBeTicked", ((ProtoChunkTickList) ticklist1).b()); + } else if (ticklist1 instanceof TickListChunk) { + nbttagcompound1.set("LiquidTicks", ((TickListChunk) ticklist1).b()); ++ // Paper start - async chunk save for unload ++ } else if (asyncsavedata != null) { ++ nbttagcompound1.set("LiquidTicks", asyncsavedata.fluidTickList); ++ // Paper end + } else { +- nbttagcompound1.set("LiquidTicks", worldserver.getFluidTickList().a(chunkcoordintpair)); ++ nbttagcompound1.set("LiquidTicks", worldserver.getFluidTickList().a(chunkcoordintpair)); // Paper - diff on method change (see getAsyncSaveData) + } + + nbttagcompound1.set("PostProcessing", a(ichunkaccess.l())); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java +index d785f44cd503d4d91589f3fc4bc8dc805dff3d41..01ae13385dd0208c9f34da8b3897b571f86305d0 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java +@@ -3,6 +3,10 @@ package net.minecraft.world.level.chunk.storage; + import com.mojang.datafixers.DataFixer; + import java.io.File; + import java.io.IOException; ++// Paper start ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.CompletionException; ++// Paper end + import java.util.function.Supplier; + import javax.annotation.Nullable; + import net.minecraft.SharedConstants; +@@ -25,32 +29,41 @@ import net.minecraft.world.level.dimension.DimensionManager; + + public class IChunkLoader implements AutoCloseable { + +- private final IOWorker a; public IOWorker getIOWorker() { return a; } // Paper - OBFHELPER ++ // Paper - OBFHELPER - nuke IOWorker + protected final DataFixer b; + @Nullable +- private PersistentStructureLegacy c; ++ private volatile PersistentStructureLegacy c; // Paper - async chunk loading ++ ++ private final Object persistentDataLock = new Object(); // Paper ++ public final RegionFileCache regionFileCache; + + public IChunkLoader(File file, DataFixer datafixer, boolean flag) { ++ this.regionFileCache = new RegionFileCache(file, flag); // Paper - nuke IOWorker + this.b = datafixer; +- this.a = new IOWorker(file, flag, "chunk"); ++ // Paper - nuke IOWorker + } + + // CraftBukkit start + private boolean check(ChunkProviderServer cps, int x, int z) throws IOException { + ChunkCoordIntPair pos = new ChunkCoordIntPair(x, z); + if (cps != null) { +- com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); +- if (cps.isLoaded(x, z)) { ++ //com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); // Paper - this function is now MT-Safe ++ if (cps.getChunkAtIfCachedImmediately(x, z) != null) { // Paper - isLoaded is a ticket level check, not a chunk loaded check! + return true; + } + } + +- NBTTagCompound nbt = read(pos); +- if (nbt != null) { +- NBTTagCompound level = nbt.getCompound("Level"); +- if (level.getBoolean("TerrainPopulated")) { +- return true; +- } ++ ++ // Paper start - prioritize ++ NBTTagCompound nbt = cps == null ? read(pos) : ++ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.loadChunkData((WorldServer)cps.getWorld(), x, z, ++ com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHER_PRIORITY, false, true).chunkData; ++ // Paper end ++ if (nbt != null) { ++ NBTTagCompound level = nbt.getCompound("Level"); ++ if (level.getBoolean("TerrainPopulated")) { ++ return true; ++ } + + ChunkStatus status = ChunkStatus.a(level.getString("Status")); + if (status != null && status.b(ChunkStatus.FEATURES)) { +@@ -81,11 +94,13 @@ public class IChunkLoader implements AutoCloseable { + if (i < 1493) { + nbttagcompound = GameProfileSerializer.a(this.b, DataFixTypes.CHUNK, nbttagcompound, i, 1493); + if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { ++ synchronized (this.persistentDataLock) { // Paper - Async chunk loading + if (this.c == null) { + this.c = PersistentStructureLegacy.a(resourcekey, (WorldPersistentData) supplier.get()); + } + + nbttagcompound = this.c.a(nbttagcompound); ++ } // Paper - Async chunk loading + } + } + +@@ -103,22 +118,20 @@ public class IChunkLoader implements AutoCloseable { + + @Nullable + public NBTTagCompound read(ChunkCoordIntPair chunkcoordintpair) throws IOException { +- return this.a.a(chunkcoordintpair); ++ return this.regionFileCache.read(chunkcoordintpair); + } + +- public void a(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) { +- this.a.a(chunkcoordintpair, nbttagcompound); ++ public void a(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws IOException { write(chunkcoordintpair, nbttagcompound); } // Paper OBFHELPER ++ public void write(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws IOException { // Paper - OBFHELPER - (Switched around for safety) ++ this.regionFileCache.write(chunkcoordintpair, nbttagcompound); + if (this.c != null) { ++ synchronized (this.persistentDataLock) { // Paper - Async chunk loading + this.c.a(chunkcoordintpair.pair()); ++ } // Paper - Async chunk loading} + } +- +- } +- +- public void i() { +- this.a.a().join(); + } + + public void close() throws IOException { +- this.a.close(); ++ this.regionFileCache.close(); + } + } +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 d50b9c9d030016f951e2ed7fb519250b7408c833..1b0535ba211904b2384cc80c02c21ed1a606e752 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 +@@ -45,6 +45,8 @@ public class RegionFile implements AutoCloseable { + protected final RegionFileBitSet freeSectors; + public final File file; // Paper + ++ public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper ++ + // Paper start - Cache chunk status + private final ChunkStatus[] statuses = new ChunkStatus[32 * 32]; + +@@ -251,7 +253,7 @@ public class RegionFile implements AutoCloseable { + return (i + 4096 - 1) / 4096; + } + +- public boolean b(ChunkCoordIntPair chunkcoordintpair) { ++ public synchronized boolean b(ChunkCoordIntPair chunkcoordintpair) { // Paper - synchronized + int i = this.getOffset(chunkcoordintpair); + + if (i == 0) { +@@ -411,6 +413,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 + this.closed = true; // Paper + try { + this.d(); +@@ -421,6 +428,10 @@ public class RegionFile implements AutoCloseable { + this.dataFile.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/RegionFileCache.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java +index 55e7e983d2c760a8052d7b3ddbdc8447f619a60f..ebb0d6988f87013ea5d523ab4a1b31cb669ccc43 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileCache.java +@@ -17,7 +17,7 @@ import net.minecraft.server.MinecraftServer; + import net.minecraft.util.ExceptionSuppressor; + import net.minecraft.world.level.ChunkCoordIntPair; + +-public final class RegionFileCache implements AutoCloseable { ++public class RegionFileCache implements AutoCloseable { // Paper - no final + + public final Long2ObjectLinkedOpenHashMap cache = new Long2ObjectLinkedOpenHashMap(); + private final File b; +@@ -30,16 +30,27 @@ public final class RegionFileCache implements AutoCloseable { + + + // Paper start +- public RegionFile getRegionFileIfLoaded(ChunkCoordIntPair chunkcoordintpair) { ++ public synchronized RegionFile getRegionFileIfLoaded(ChunkCoordIntPair chunkcoordintpair) { // Paper - synchronize for async io + return this.cache.getAndMoveToFirst(ChunkCoordIntPair.pair(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); + } + + // Paper end +- public RegionFile getFile(ChunkCoordIntPair chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - private > public ++ public synchronized RegionFile getFile(ChunkCoordIntPair chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - private > public, synchronize ++ // Paper start - add lock parameter ++ return this.getFile(chunkcoordintpair, existingOnly, false); ++ } ++ public synchronized RegionFile getFile(ChunkCoordIntPair chunkcoordintpair, boolean existingOnly, boolean lock) throws IOException { ++ // Paper end + long i = ChunkCoordIntPair.pair(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); + RegionFile regionfile = (RegionFile) this.cache.getAndMoveToFirst(i); + + if (regionfile != null) { ++ // Paper start ++ if (lock) { ++ // must be in this synchronized block ++ regionfile.fileLock.lock(); ++ } ++ // Paper end + return regionfile; + } else { + if (this.cache.size() >= com.destroystokyo.paper.PaperConfig.regionFileCacheSize) { // Paper - configurable +@@ -55,6 +66,12 @@ public final class RegionFileCache implements AutoCloseable { + RegionFile regionfile1 = new RegionFile(file, this.b, this.c); + + this.cache.putAndMoveToFirst(i, regionfile1); ++ // Paper start ++ if (lock) { ++ // must be in this synchronized block ++ regionfile1.fileLock.lock(); ++ } ++ // Paper end + return regionfile1; + } + } +@@ -130,11 +147,12 @@ public final class RegionFileCache implements AutoCloseable { + @Nullable + public NBTTagCompound read(ChunkCoordIntPair chunkcoordintpair) throws IOException { + // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing +- RegionFile regionfile = this.getFile(chunkcoordintpair, true); ++ RegionFile regionfile = this.getFile(chunkcoordintpair, true, true); // Paper + if (regionfile == null) { + return null; + } + // CraftBukkit end ++ try { // Paper + DataInputStream datainputstream = regionfile.a(chunkcoordintpair); + // Paper start + if (regionfile.isOversized(chunkcoordintpair.x, chunkcoordintpair.z)) { +@@ -172,10 +190,14 @@ public final class RegionFileCache implements AutoCloseable { + } + + return nbttagcompound; ++ } finally { // Paper start ++ regionfile.fileLock.unlock(); ++ } // Paper end + } + + protected void write(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws IOException { +- RegionFile regionfile = this.getFile(chunkcoordintpair, false); // CraftBukkit ++ RegionFile regionfile = this.getFile(chunkcoordintpair, false, true); // CraftBukkit // Paper ++ try { // Paper + int attempts = 0; Exception laste = null; while (attempts++ < 5) { try { // Paper + DataOutputStream dataoutputstream = regionfile.c(chunkcoordintpair); + Throwable throwable = null; +@@ -214,9 +236,12 @@ public final class RegionFileCache implements AutoCloseable { + MinecraftServer.LOGGER.error("Failed to save chunk", laste); + } + // Paper end ++ } finally { // Paper start ++ regionfile.fileLock.unlock(); ++ } // Paper end + } + +- public void close() throws IOException { ++ public synchronized void close() throws IOException { // Paper -> synchronized + ExceptionSuppressor exceptionsuppressor = new ExceptionSuppressor<>(); + ObjectIterator objectiterator = this.cache.values().iterator(); + +@@ -243,4 +268,12 @@ public final class RegionFileCache implements AutoCloseable { + } + + } ++ ++ // CraftBukkit start ++ public synchronized boolean chunkExists(ChunkCoordIntPair pos) throws IOException { // Paper - synchronize ++ RegionFile regionfile = getFile(pos, true); ++ ++ return regionfile != null ? regionfile.chunkExists(pos) : false; ++ } ++ // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java +index 8ad97a8a2189553da88810380b1c240079eacc93..d3b9a9e4695655860c72db5f2188472681e8d37a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java +@@ -30,28 +30,29 @@ import net.minecraft.world.level.World; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + +-public class RegionFileSection implements AutoCloseable { ++public class RegionFileSection extends RegionFileCache implements AutoCloseable { // Paper - nuke IOWorker + + private static final Logger LOGGER = LogManager.getLogger(); +- private final IOWorker b; ++ // Paper - nuke IOWorker + private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); +- private final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); ++ public final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); // Paper - private -> public + private final Function> e; + private final Function f; + private final DataFixer g; + private final DataFixTypes h; + + public RegionFileSection(File file, Function> function, Function function1, DataFixer datafixer, DataFixTypes datafixtypes, boolean flag) { ++ super(file, flag); // Paper - nuke IOWorker + this.e = function; + this.f = function1; + this.g = datafixer; + this.h = datafixtypes; +- this.b = new IOWorker(file, flag, file.getName()); ++ //this.b = new IOWorker(file, flag, file.getName()); // Paper - nuke IOWorker + } + + protected void a(BooleanSupplier booleansupplier) { + while (!this.d.isEmpty() && booleansupplier.getAsBoolean()) { +- ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(this.d.firstLong()).r(); ++ ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(this.d.firstLong()).r(); // Paper - conflict here to avoid obfhelpers + + this.d(chunkcoordintpair); + } +@@ -105,13 +106,18 @@ public class RegionFileSection implements AutoCloseable { + } + + private void b(ChunkCoordIntPair chunkcoordintpair) { +- this.a(chunkcoordintpair, DynamicOpsNBT.a, this.c(chunkcoordintpair)); ++ // Paper start - load data in function ++ this.loadInData(chunkcoordintpair, this.c(chunkcoordintpair)); ++ } ++ public void loadInData(ChunkCoordIntPair chunkPos, NBTTagCompound compound) { ++ this.a(chunkPos, DynamicOpsNBT.a, compound); ++ // Paper end + } + + @Nullable + private NBTTagCompound c(ChunkCoordIntPair chunkcoordintpair) { + try { +- return this.b.a(chunkcoordintpair); ++ return this.read(chunkcoordintpair); // Paper - nuke IOWorker + } catch (IOException ioexception) { + RegionFileSection.LOGGER.error("Error reading chunk {} data from disk", chunkcoordintpair, ioexception); + return null; +@@ -157,17 +163,31 @@ public class RegionFileSection implements AutoCloseable { + } + + private void d(ChunkCoordIntPair chunkcoordintpair) { +- Dynamic dynamic = this.a(chunkcoordintpair, DynamicOpsNBT.a); ++ Dynamic dynamic = this.a(chunkcoordintpair, DynamicOpsNBT.a); // Paper - conflict here to avoid adding obfhelpers :) + NBTBase nbtbase = (NBTBase) dynamic.getValue(); + + if (nbtbase instanceof NBTTagCompound) { +- this.b.a(chunkcoordintpair, (NBTTagCompound) nbtbase); ++ try { this.write(chunkcoordintpair, (NBTTagCompound) nbtbase); } catch (IOException ioexception) { RegionFileSection.LOGGER.error("Error writing data to disk", ioexception); } // Paper - nuke IOWorker // TODO make this write async + } else { + RegionFileSection.LOGGER.error("Expected compound tag, got {}", nbtbase); + } + + } + ++ // Paper start - internal get data function, copied from above ++ private NBTTagCompound getDataInternal(ChunkCoordIntPair chunkcoordintpair) { ++ Dynamic dynamic = this.a(chunkcoordintpair, DynamicOpsNBT.a); ++ NBTBase nbtbase = (NBTBase) dynamic.getValue(); ++ ++ if (nbtbase instanceof NBTTagCompound) { ++ return (NBTTagCompound)nbtbase; ++ } else { ++ RegionFileSection.LOGGER.error("Expected compound tag, got {}", nbtbase); ++ } ++ return null; ++ } ++ // Paper end ++ + private Dynamic a(ChunkCoordIntPair chunkcoordintpair, DynamicOps dynamicops) { + Map map = Maps.newHashMap(); + +@@ -213,9 +233,9 @@ public class RegionFileSection implements AutoCloseable { + public void a(ChunkCoordIntPair chunkcoordintpair) { + if (!this.d.isEmpty()) { + for (int i = 0; i < 16; ++i) { +- long j = SectionPosition.a(chunkcoordintpair, i).s(); ++ long j = SectionPosition.a(chunkcoordintpair, i).s(); // Paper - conflict here to avoid obfhelpers + +- if (this.d.contains(j)) { ++ if (this.d.contains(j)) { // Paper - conflict here to avoid obfhelpers + this.d(chunkcoordintpair); + return; + } +@@ -224,7 +244,26 @@ public class RegionFileSection implements AutoCloseable { + + } + +- public void close() throws IOException { +- this.b.close(); ++// Paper start - nuke IOWorker ++// public void close() throws IOException { ++// this.b.close(); ++// } ++// Paper end ++ ++ // Paper start - get data function ++ public NBTTagCompound getData(ChunkCoordIntPair chunkcoordintpair) { ++ // Note: Copied from above ++ // This is checking if the data exists, then it builds it later in getDataInternal(ChunkCoordIntPair) ++ if (!this.d.isEmpty()) { ++ for (int i = 0; i < 16; ++i) { ++ long j = SectionPosition.a(chunkcoordintpair, i).s(); ++ ++ if (this.d.contains(j)) { ++ return this.getDataInternal(chunkcoordintpair); ++ } ++ } ++ } ++ return null; + } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index daa5c52a690ce33d2b2cd32c4fbaa147f25dd144..e880ce299bd81ad790da7b94fb28c49a5ebab1e7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -563,22 +563,23 @@ public class CraftWorld implements World { + return true; + } + +- net.minecraft.world.level.chunk.storage.RegionFile file; +- try { +- file = world.getChunkProvider().playerChunkMap.getIOWorker().getRegionFileCache().getFile(chunkPos, false); +- } catch (IOException ex) { +- throw new RuntimeException(ex); +- } ++ ChunkStatus status = world.getChunkProvider().playerChunkMap.getStatusOnDiskNoLoad(x, z); // Paper - async io - move to own method + +- ChunkStatus status = file.getStatusIfCached(x, z); +- if (!file.chunkExists(chunkPos) || (status != null && status != ChunkStatus.FULL)) { ++ // Paper start - async io ++ if (status == ChunkStatus.EMPTY) { ++ // does not exist on disk + return false; + } + ++ if (status == null) { // at this stage we don't know what it is on disk + IChunkAccess chunk = world.getChunkProvider().getChunkAt(x, z, ChunkStatus.EMPTY, true); + if (!(chunk instanceof ProtoChunkExtension) && !(chunk instanceof net.minecraft.world.level.chunk.Chunk)) { + return false; + } ++ } else if (status != ChunkStatus.FULL) { ++ return false; // not full status on disk ++ } ++ // Paper end + + // fall through to load + // we do this so we do not re-read the chunk data on disk +@@ -2501,6 +2502,34 @@ public class CraftWorld implements World { + public DragonBattle getEnderDragonBattle() { + return (getHandle().getDragonBattle() == null) ? null : new CraftDragonBattle(getHandle().getDragonBattle()); + } ++ // Paper start ++ @Override ++ public CompletableFuture getChunkAtAsync(int x, int z, boolean gen, boolean urgent) { ++ if (Bukkit.isPrimaryThread()) { ++ net.minecraft.world.level.chunk.Chunk immediate = this.world.getChunkProvider().getChunkAtIfLoadedImmediately(x, z); ++ if (immediate != null) { ++ return CompletableFuture.completedFuture(immediate.getBukkitChunk()); ++ } ++ } else { ++ CompletableFuture future = new CompletableFuture(); ++ world.getMinecraftServer().execute(() -> { ++ getChunkAtAsync(x, z, gen, urgent).whenComplete((chunk, err) -> { ++ if (err != null) { ++ future.completeExceptionally(err); ++ } else { ++ future.complete(chunk); ++ } ++ }); ++ }); ++ return future; ++ } ++ ++ return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { ++ net.minecraft.world.level.chunk.Chunk chunk = (net.minecraft.world.level.chunk.Chunk) either.left().orElse(null); ++ return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); ++ }, net.minecraft.server.MinecraftServer.getServer()); ++ } ++ // Paper end + + // Spigot start + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 93bbf63e9d38f32d5528c7693633d4b65655bb9d..266b2cbd6bfaf10743929a1eeb9732a5d1fb4c62 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -12,6 +12,9 @@ import net.minecraft.nbt.NBTBase; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.PlayerChunk; ++import net.minecraft.server.level.PlayerChunkMap; ++import net.minecraft.server.level.TicketType; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityAreaEffectCloud; +@@ -144,6 +147,7 @@ import net.minecraft.world.entity.vehicle.EntityMinecartHopper; + import net.minecraft.world.entity.vehicle.EntityMinecartMobSpawner; + import net.minecraft.world.entity.vehicle.EntityMinecartRideable; + import net.minecraft.world.entity.vehicle.EntityMinecartTNT; ++import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.phys.AxisAlignedBB; + import org.bukkit.Chunk; // Paper + import org.bukkit.EntityEffect; +@@ -508,6 +512,28 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + entity.setHeadRotation(yaw); + } + ++ @Override// Paper start ++ public java.util.concurrent.CompletableFuture teleportAsync(Location loc, @javax.annotation.Nonnull org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { ++ PlayerChunkMap playerChunkMap = ((CraftWorld) loc.getWorld()).getHandle().getChunkProvider().playerChunkMap; ++ java.util.concurrent.CompletableFuture future = new java.util.concurrent.CompletableFuture<>(); ++ ++ loc.getWorld().getChunkAtAsyncUrgently(loc).thenCompose(chunk -> { ++ ChunkCoordIntPair pair = new ChunkCoordIntPair(chunk.getX(), chunk.getZ()); ++ ((CraftWorld) loc.getWorld()).getHandle().getChunkProvider().addTicketAtLevel(TicketType.POST_TELEPORT, pair, 31, 0); ++ PlayerChunk updatingChunk = playerChunkMap.getUpdatingChunk(pair.pair()); ++ if (updatingChunk != null) { ++ return updatingChunk.getEntityTickingFuture(); ++ } else { ++ return java.util.concurrent.CompletableFuture.completedFuture(com.mojang.datafixers.util.Either.left(((org.bukkit.craftbukkit.CraftChunk)chunk).getHandle())); ++ } ++ }).thenAccept((chunk) -> future.complete(teleport(loc, cause))).exceptionally(ex -> { ++ future.completeExceptionally(ex); ++ return null; ++ }); ++ return future; ++ } ++ // Paper end ++ + @Override + public boolean teleport(Location location) { + return teleport(location, TeleportCause.PLUGIN); +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index abaefa0b71104756e4b458abefe13d179e7a1724..58e50bf0fb0f309227e1f4c1f6bb11c01d8e08d3 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -6,6 +6,7 @@ import java.lang.management.ThreadInfo; + import java.util.logging.Level; + import java.util.logging.Logger; + import com.destroystokyo.paper.PaperConfig; ++import com.destroystokyo.paper.io.chunk.ChunkTaskManager; // Paper + import net.minecraft.server.MinecraftServer; + import org.bukkit.Bukkit; + +@@ -116,6 +117,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 ++ ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper + dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // diff --git a/patches/server-unmapped/0001/0371-Use-getChunkIfLoadedImmediately-in-places.patch b/patches/server-unmapped/0001/0371-Use-getChunkIfLoadedImmediately-in-places.patch new file mode 100644 index 0000000000..52c1e870ae --- /dev/null +++ b/patches/server-unmapped/0001/0371-Use-getChunkIfLoadedImmediately-in-places.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 8 Jul 2019 00:13:36 -0700 +Subject: [PATCH] Use getChunkIfLoadedImmediately in places + +This prevents us from hitting chunk loads for chunks at or less-than +ticket level 33 (yes getChunkIfLoaded will actually perform a chunk +load in that case). + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index a68be9b72daca6353d51141a8771e00240375315..fe662ea8acb873aac2fc3b29addbef77854494ed 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -207,7 +207,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + @Override public Chunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI +- return this.chunkProvider.getChunkAt(x, z, false); ++ return this.chunkProvider.getChunkAtIfLoadedImmediately(x, z); // Paper + } + + // Paper start - Asynchronous IO +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index fa0b7edf42243d53d9dc897903b9b9e902b33cf7..eee5b3e4645ae41f63aba8898c58f43402d31b73 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1244,7 +1244,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + speed = player.abilities.walkSpeed * 10f; + } + // Paper start - Prevent moving into unloaded chunks +- if (player.world.paperConfig.preventMovingIntoUnloadedChunks && (this.player.locX() != toX || this.player.locZ() != toZ) && !worldserver.isChunkLoaded((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4)) { ++ if (player.world.paperConfig.preventMovingIntoUnloadedChunks && (this.player.locX() != toX || this.player.locZ() != toZ) && worldserver.getChunkIfLoadedImmediately((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4) == null) { // Paper - use getIfLoadedImmediately + this.internalTeleport(this.player.locX(), this.player.locY(), this.player.locZ(), this.player.yaw, this.player.pitch, Collections.emptySet()); + return; + } +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 1e7e64fdd058e7fe2bb96825d7d5170ccc4c0b50..b620d7e0d824c8d0758a66a8fbe872c3e45103d2 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -164,6 +164,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return (CraftServer) Bukkit.getServer(); + } + ++ // Paper start ++ @Override ++ public boolean isChunkLoaded(int x, int z) { ++ return ((WorldServer)this).getChunkIfLoaded(x, z) != null; ++ } ++ // Paper end ++ + public ResourceKey getTypeKey() { + return typeKey; + } +@@ -1062,14 +1069,14 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + } + + public boolean p(BlockPosition blockposition) { +- return isOutsideWorld(blockposition) ? false : this.getChunkProvider().isLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return isOutsideWorld(blockposition) ? false : isChunkLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); // Paper + } + + public boolean a(BlockPosition blockposition, Entity entity, EnumDirection enumdirection) { + if (isOutsideWorld(blockposition)) { + return false; + } else { +- IChunkAccess ichunkaccess = this.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4, ChunkStatus.FULL, false); ++ IChunkAccess ichunkaccess = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); // Paper + + return ichunkaccess == null ? false : ichunkaccess.getType(blockposition).a((IBlockAccess) this, blockposition, entity, enumdirection); + } +@@ -1190,7 +1197,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + for (int i1 = i; i1 < j; ++i1) { + for (int j1 = k; j1 < l; ++j1) { +- Chunk chunk = ichunkprovider.a(i1, j1); ++ Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + + if (chunk != null) { + chunk.a(oclass, axisalignedbb, list, predicate); +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 58d22363124a9343188d8c19476e5a92f2f0b80b..53d0541aba207b5eaea2e49edbb56df918d30333 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -140,9 +140,10 @@ public class ActivationRange + { + for ( int j1 = k; j1 <= l; ++j1 ) + { +- if ( world.getWorld().isChunkLoaded( i1, j1 ) ) ++ Chunk chunk = (Chunk) world.getChunkIfLoadedImmediately( i1, j1 ); ++ if ( chunk != null ) + { +- activateChunkEntities( world.getChunkAt( i1, j1 ) ); ++ activateChunkEntities( chunk ); + } + } + } diff --git a/patches/server-unmapped/0001/0372-Reduce-sync-loads.patch b/patches/server-unmapped/0001/0372-Reduce-sync-loads.patch new file mode 100644 index 0000000000..d386f73120 --- /dev/null +++ b/patches/server-unmapped/0001/0372-Reduce-sync-loads.patch @@ -0,0 +1,342 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 19 Jul 2019 03:29:14 -0700 +Subject: [PATCH] Reduce sync loads + +This reduces calls to getChunkAt which would load chunks. + +This patch also adds a tool to find calls which are doing this, however +it must be enabled by setting the startup flag -Dpaper.debug-sync-loads=true + +To get a debug log for sync loads, the command is /paper syncloadinfo + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index f657e9b6bb3d24a6c77ef584711a003d1eea0341..eb1e86e8bb0f421e3686ffa02a4015a588107863 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -1,6 +1,7 @@ + package com.destroystokyo.paper; + + import com.destroystokyo.paper.io.chunk.ChunkTaskManager; ++import com.destroystokyo.paper.io.SyncLoadFinder; + import com.google.common.base.Functions; + import com.google.common.base.Joiner; + import com.google.common.collect.ImmutableSet; +@@ -8,6 +9,10 @@ import com.google.common.collect.Iterables; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import net.minecraft.resources.MinecraftKey; ++import com.google.gson.JsonObject; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonWriter; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkProviderServer; + import net.minecraft.server.level.PlayerChunk; +@@ -29,6 +34,9 @@ import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.entity.Player; + + import java.io.File; ++import java.io.FileOutputStream; ++import java.io.PrintStream; ++import java.io.StringWriter; + import java.time.LocalDateTime; + import java.time.format.DateTimeFormatter; + import java.util.ArrayList; +@@ -44,7 +52,7 @@ import java.util.stream.Collectors; + + public class PaperCommand extends Command { + private static final String BASE_PERM = "bukkit.command.paper."; +- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting").build(); ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo").build(); + + public PaperCommand(String name) { + super(name); +@@ -162,6 +170,9 @@ public class PaperCommand extends Command { + case "chunkinfo": + doChunkInfo(sender, args); + break; ++ case "syncloadinfo": ++ this.doSyncLoadInfo(sender, args); ++ break; + case "ver": + if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) + case "version": +@@ -179,6 +190,40 @@ public class PaperCommand extends Command { + return true; + } + ++ private void doSyncLoadInfo(CommandSender sender, String[] args) { ++ if (!SyncLoadFinder.ENABLED) { ++ sender.sendMessage(ChatColor.RED + "This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set."); ++ return; ++ } ++ File file = new File(new File(new File("."), "debug"), ++ "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); ++ file.getParentFile().mkdirs(); ++ sender.sendMessage(ChatColor.GREEN + "Writing sync load info to " + file.toString()); ++ ++ ++ try { ++ final JsonObject data = SyncLoadFinder.serialize(); ++ ++ 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, "UTF-8") ++ ) { ++ out.print(fileData); ++ } ++ sender.sendMessage(ChatColor.GREEN + "Successfully written sync load information!"); ++ } catch (Throwable thr) { ++ sender.sendMessage(ChatColor.RED + "Failed to write sync load information"); ++ thr.printStackTrace(); ++ } ++ } ++ + private void doChunkInfo(CommandSender sender, String[] args) { + List worlds; + if (args.length < 2 || args[1].equals("*")) { +diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d381f91cf105bfc01846ada90da8971a3618e784 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +@@ -0,0 +1,171 @@ ++package com.destroystokyo.paper.io; ++ ++import com.google.gson.JsonArray; ++import com.google.gson.JsonObject; ++import com.mojang.datafixers.util.Pair; ++import it.unimi.dsi.fastutil.longs.Long2IntMap; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; ++ ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; ++import java.util.WeakHashMap; ++import net.minecraft.world.level.World; ++ ++public class SyncLoadFinder { ++ ++ public static final boolean ENABLED = Boolean.getBoolean("paper.debug-sync-loads"); ++ ++ private static final WeakHashMap> SYNC_LOADS = new WeakHashMap<>(); ++ ++ private static final class SyncLoadInformation { ++ ++ public int times; ++ ++ public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap(); ++ } ++ ++ public static void logSyncLoad(final World world, final int chunkX, final int chunkZ) { ++ if (!ENABLED) { ++ return; ++ } ++ ++ final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace()); ++ ++ SYNC_LOADS.compute(world, (final World keyInMap, Object2ObjectOpenHashMap map) -> { ++ if (map == null) { ++ map = new Object2ObjectOpenHashMap<>(); ++ } ++ ++ map.compute(stacktrace, (ThrowableWithEquals keyInMap0, SyncLoadInformation valueInMap) -> { ++ if (valueInMap == null) { ++ valueInMap = new SyncLoadInformation(); ++ } ++ ++ ++valueInMap.times; ++ ++ valueInMap.coordinateTimes.compute(IOUtil.getCoordinateKey(chunkX, chunkZ), (Long keyInMap1, Integer valueInMap1) -> { ++ return valueInMap1 == null ? Integer.valueOf(1) : Integer.valueOf(valueInMap1.intValue() + 1); ++ }); ++ ++ return valueInMap; ++ }); ++ ++ return map; ++ }); ++ } ++ ++ public static JsonObject serialize() { ++ final JsonObject ret = new JsonObject(); ++ ++ final JsonArray worldsData = new JsonArray(); ++ ++ for (final Map.Entry> entry : SYNC_LOADS.entrySet()) { ++ final World world = entry.getKey(); ++ ++ final JsonObject worldData = new JsonObject(); ++ ++ worldData.addProperty("name", world.getWorld().getName()); ++ ++ final List> data = new ArrayList<>(); ++ ++ entry.getValue().forEach((ThrowableWithEquals stacktrace, SyncLoadInformation times) -> { ++ data.add(new Pair<>(stacktrace, times)); ++ }); ++ ++ data.sort((Pair pair1, Pair pair2) -> { ++ return Integer.compare(pair2.getSecond().times, pair1.getSecond().times); // reverse order ++ }); ++ ++ final JsonArray stacktraces = new JsonArray(); ++ ++ for (Pair pair : data) { ++ final JsonObject stacktrace = new JsonObject(); ++ ++ stacktrace.addProperty("times", pair.getSecond().times); ++ ++ final JsonArray traces = new JsonArray(); ++ ++ for (StackTraceElement element : pair.getFirst().stacktrace) { ++ traces.add(String.valueOf(element)); ++ } ++ ++ stacktrace.add("stacktrace", traces); ++ ++ final JsonArray coordinates = new JsonArray(); ++ ++ for (Long2IntMap.Entry coordinate : pair.getSecond().coordinateTimes.long2IntEntrySet()) { ++ final long key = coordinate.getLongKey(); ++ final int times = coordinate.getIntValue(); ++ coordinates.add("(" + IOUtil.getCoordinateX(key) + "," + IOUtil.getCoordinateZ(key) + "): " + times); ++ } ++ ++ stacktrace.add("coordinates", coordinates); ++ ++ stacktraces.add(stacktrace); ++ } ++ ++ ++ worldData.add("stacktraces", stacktraces); ++ worldsData.add(worldData); ++ } ++ ++ ret.add("worlds", worldsData); ++ ++ return ret; ++ } ++ ++ static final class ThrowableWithEquals { ++ ++ private final StackTraceElement[] stacktrace; ++ private final int hash; ++ ++ public ThrowableWithEquals(final StackTraceElement[] stacktrace) { ++ this.stacktrace = stacktrace; ++ this.hash = ThrowableWithEquals.hash(stacktrace); ++ } ++ ++ public static int hash(final StackTraceElement[] stacktrace) { ++ int hash = 0; ++ ++ for (int i = 0; i < stacktrace.length; ++i) { ++ hash *= 31; ++ hash += stacktrace[i].hashCode(); ++ } ++ ++ return hash; ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.hash; ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (obj == null || obj.getClass() != this.getClass()) { ++ return false; ++ } ++ ++ final ThrowableWithEquals other = (ThrowableWithEquals)obj; ++ final StackTraceElement[] otherStackTrace = other.stacktrace; ++ ++ if (this.stacktrace.length != otherStackTrace.length || this.hash != other.hash) { ++ return false; ++ } ++ ++ if (this == obj) { ++ return true; ++ } ++ ++ for (int i = 0; i < this.stacktrace.length; ++i) { ++ if (!this.stacktrace[i].equals(otherStackTrace[i])) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index 9d73681b22fda4d8b6bc7876b73012ef5b2b69d8..e108705575669a97fffd56f3c215fde09b0f180a 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -494,6 +494,7 @@ public class ChunkProviderServer extends IChunkProvider { + this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z); + // Paper end ++ com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.world, x, z); // Paper - sync load info + this.world.timings.syncChunkLoad.startTiming(); // Paper + this.serverThreadQueue.awaitTasks(completablefuture::isDone); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index fe662ea8acb873aac2fc3b29addbef77854494ed..71967bc86c2c2ad6bcafc80a5f8ab31c33f022e6 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -282,6 +282,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { + }; + public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager; + // Paper end ++ // Paper start ++ @Override ++ public boolean isChunkLoaded(int x, int z) { ++ return this.getChunkProvider().getChunkAtIfLoadedImmediately(x, z) != null; ++ } ++ // Paper end + + // Add env and gen to constructor, WorldData -> WorldDataServer + public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index b620d7e0d824c8d0758a66a8fbe872c3e45103d2..6781b25cc8e15be2556bb1bb8dc8c18c106b40ec 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -1130,7 +1130,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + for (int i1 = i; i1 <= j; ++i1) { + for (int j1 = k; j1 <= l; ++j1) { +- Chunk chunk = ichunkprovider.getChunkAt(i1, j1, false); ++ Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + + if (chunk != null) { + chunk.a(entity, axisalignedbb, list, predicate); +@@ -1151,7 +1151,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + for (int i1 = i; i1 < j; ++i1) { + for (int j1 = k; j1 < l; ++j1) { +- Chunk chunk = this.getChunkProvider().getChunkAt(i1, j1, false); ++ Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + + if (chunk != null) { + chunk.a(entitytypes, axisalignedbb, list, predicate); +@@ -1174,7 +1174,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + for (int i1 = i; i1 < j; ++i1) { + for (int j1 = k; j1 < l; ++j1) { +- Chunk chunk = ichunkprovider.getChunkAt(i1, j1, false); ++ Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + + if (chunk != null) { + chunk.a(oclass, axisalignedbb, list, predicate); diff --git a/patches/server-unmapped/0001/0373-Implement-alternative-item-despawn-rate.patch b/patches/server-unmapped/0001/0373-Implement-alternative-item-despawn-rate.patch new file mode 100644 index 0000000000..4af6ed58d8 --- /dev/null +++ b/patches/server-unmapped/0001/0373-Implement-alternative-item-despawn-rate.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Mon, 3 Jun 2019 02:02:39 -0400 +Subject: [PATCH] Implement alternative item-despawn-rate + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 1ee2cced100626e48eb36ee14f84b9257c79a2f8..b913cd2dd0cd1b369b3f7b5a9d8b1be73f6d7920 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -1,10 +1,15 @@ + package com.destroystokyo.paper; + + import java.util.Arrays; ++import java.util.EnumMap; ++import java.util.HashMap; + import java.util.List; ++import java.util.Map; + + import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode; + import org.bukkit.Bukkit; ++import org.bukkit.Material; ++import org.bukkit.configuration.ConfigurationSection; + import org.bukkit.configuration.file.YamlConfiguration; + import org.spigotmc.SpigotWorldConfig; + +@@ -512,4 +517,52 @@ public class PaperWorldConfig { + private void disableRelativeProjectileVelocity() { + disableRelativeProjectileVelocity = getBoolean("game-mechanics.disable-relative-projectile-velocity", false); + } ++ ++ public boolean altItemDespawnRateEnabled; ++ public Map altItemDespawnRateMap; ++ private void altItemDespawnRate() { ++ String path = "alt-item-despawn-rate"; ++ ++ altItemDespawnRateEnabled = getBoolean(path + ".enabled", false); ++ ++ Map altItemDespawnRateMapDefault = new EnumMap<>(Material.class); ++ altItemDespawnRateMapDefault.put(Material.COBBLESTONE, 300); ++ for (Material key : altItemDespawnRateMapDefault.keySet()) { ++ config.addDefault("world-settings.default." + path + ".items." + key, altItemDespawnRateMapDefault.get(key)); ++ } ++ ++ Map rawMap = new HashMap<>(); ++ try { ++ ConfigurationSection mapSection = config.getConfigurationSection("world-settings." + worldName + "." + path + ".items"); ++ if (mapSection == null) { ++ mapSection = config.getConfigurationSection("world-settings.default." + path + ".items"); ++ } ++ for (String key : mapSection.getKeys(false)) { ++ int val = mapSection.getInt(key); ++ rawMap.put(key, val); ++ } ++ } ++ catch (Exception e) { ++ logError("alt-item-despawn-rate was malformatted"); ++ altItemDespawnRateEnabled = false; ++ } ++ ++ altItemDespawnRateMap = new EnumMap<>(Material.class); ++ if (!altItemDespawnRateEnabled) { ++ return; ++ } ++ ++ for(String key : rawMap.keySet()) { ++ try { ++ altItemDespawnRateMap.put(Material.valueOf(key), rawMap.get(key)); ++ } catch (Exception e) { ++ logError("Could not add item " + key + " to altItemDespawnRateMap: " + e.getMessage()); ++ } ++ } ++ if(altItemDespawnRateEnabled) { ++ for(Material key : altItemDespawnRateMap.keySet()) { ++ log("Alternative item despawn rate of " + key + ": " + altItemDespawnRateMap.get(key)); ++ } ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityItem.java b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +index 02e4d6891adc902f73ed349f15dae3a429bd283a..0b2e6e72a85e05f239d56afb6785c91da5b25d55 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityItem.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +@@ -33,6 +33,7 @@ import net.minecraft.world.phys.Vec3D; + + // CraftBukkit start + import net.minecraft.server.MinecraftServer; ++import org.bukkit.Material; // Paper + import org.bukkit.event.entity.EntityPickupItemEvent; + import org.bukkit.event.player.PlayerPickupItemEvent; + // CraftBukkit end +@@ -161,7 +162,7 @@ public class EntityItem extends Entity { + } + } + +- if (!this.world.isClientSide && this.age >= world.spigotConfig.itemDespawnRate) { // Spigot ++ if (!this.world.isClientSide && this.age >= this.getDespawnRate()) { // Spigot // Paper + // CraftBukkit start - fire ItemDespawnEvent + if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { + this.age = 0; +@@ -185,7 +186,7 @@ public class EntityItem extends Entity { + this.lastTick = MinecraftServer.currentTick; + // CraftBukkit end + +- if (!this.world.isClientSide && this.age >= world.spigotConfig.itemDespawnRate) { // Spigot ++ if (!this.world.isClientSide && this.age >= this.getDespawnRate()) { // Spigot // Paper + // CraftBukkit start - fire ItemDespawnEvent + if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { + this.age = 0; +@@ -535,9 +536,16 @@ public class EntityItem extends Entity { + + public void s() { + this.o(); +- this.age = world.spigotConfig.itemDespawnRate - 1; // Spigot ++ this.age = this.getDespawnRate() - 1; // Spigot // Paper + } + ++ // Paper start ++ public int getDespawnRate(){ ++ Material material = this.getItemStack().getBukkitStack().getType(); ++ return world.paperConfig.altItemDespawnRateMap.getOrDefault(material, world.spigotConfig.itemDespawnRate); ++ } ++ // Paper end ++ + @Override + public Packet P() { + return new PacketPlayOutSpawnEntity(this); diff --git a/patches/server-unmapped/0001/0374-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch b/patches/server-unmapped/0001/0374-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch new file mode 100644 index 0000000000..8c630c0def --- /dev/null +++ b/patches/server-unmapped/0001/0374-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sun, 14 Jul 2019 21:05:03 -0500 +Subject: [PATCH] Do less work if we have a custom Bukkit generator + +If the Bukkit generator already has a spawn, use it immediately instead +of spending time generating one that we won't use + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index baafdffabf28415da66eccb6e14466cd428f5832..0a407b7d2c87e2fc745eedf7b3ea794ab0211716 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -629,12 +629,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { +- return biomebase.b().b(); +- }, random); +- ChunkCoordIntPair chunkcoordintpair = blockposition == null ? new ChunkCoordIntPair(0, 0) : new ChunkCoordIntPair(blockposition); ++ // Paper start - moved down + // CraftBukkit start + if (worldserver.generator != null) { + Random rand = new Random(worldserver.getSeed()); +@@ -650,6 +645,15 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { ++ return biomebase.b().b(); ++ }, random); ++ ChunkCoordIntPair chunkcoordintpair = blockposition == null ? new ChunkCoordIntPair(0, 0) : new ChunkCoordIntPair(blockposition); ++ // Paper end + + if (blockposition == null) { + MinecraftServer.LOGGER.warn("Unable to find spawn biome"); diff --git a/patches/server-unmapped/0001/0375-Fix-MC-158900.patch b/patches/server-unmapped/0001/0375-Fix-MC-158900.patch new file mode 100644 index 0000000000..cefdc602b0 --- /dev/null +++ b/patches/server-unmapped/0001/0375-Fix-MC-158900.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 13 Aug 2019 06:35:17 -0700 +Subject: [PATCH] Fix MC-158900 + +The problem was we were checking isExpired() on the entry, but if it +was expired at that point, then it would be null. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index e029439803df82354de47cd6f047e98578ae9f95..54e49dcc56281d02203fdcdb20906a4ee0e43c05 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -607,8 +607,10 @@ public abstract class PlayerList { + Player player = entity.getBukkitEntity(); + PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.networkManager.getRawAddress()).getAddress()); + +- if (getProfileBans().isBanned(gameprofile) && !getProfileBans().get(gameprofile).hasExpired()) { +- GameProfileBanEntry gameprofilebanentry = (GameProfileBanEntry) this.k.get(gameprofile); ++ // Paper start - Fix MC-158900 ++ GameProfileBanEntry gameprofilebanentry; ++ if (getProfileBans().isBanned(gameprofile) && (gameprofilebanentry = getProfileBans().get(gameprofile)) != null) { ++ // Paper end + + chatmessage = new ChatMessage("multiplayer.disconnect.banned.reason", new Object[]{gameprofilebanentry.getReason()}); + if (gameprofilebanentry.getExpires() != null) { diff --git a/patches/server-unmapped/0001/0376-implement-optional-per-player-mob-spawns.patch b/patches/server-unmapped/0001/0376-implement-optional-per-player-mob-spawns.patch new file mode 100644 index 0000000000..9457a12ff5 --- /dev/null +++ b/patches/server-unmapped/0001/0376-implement-optional-per-player-mob-spawns.patch @@ -0,0 +1,848 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Mon, 19 Aug 2019 01:27:58 +0500 +Subject: [PATCH] implement optional per player mob spawns + + +diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +index 2da28784ee427001b1137c859f0b4c350abd3110..c5f594d45012016d99b83a778a2b9d20a7c086ac 100644 +--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java ++++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +@@ -57,6 +57,7 @@ public class WorldTimingsHandler { + + + public final Timing miscMobSpawning; ++ public final Timing playerMobDistanceMapUpdate; + + public final Timing poiUnload; + public final Timing chunkUnload; +@@ -122,6 +123,7 @@ public class WorldTimingsHandler { + + + miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc"); ++ playerMobDistanceMapUpdate = Timings.ofSafe(name + "Per Player Mob Spawning - Distance Map Update"); + + poiUnload = Timings.ofSafe(name + "Chunk unload - POI"); + chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk"); +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index b913cd2dd0cd1b369b3f7b5a9d8b1be73f6d7920..6aec502eb529d4090306e12e837117cde7e114eb 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -565,4 +565,9 @@ public class PaperWorldConfig { + } + } + } ++ ++ public boolean perPlayerMobSpawns = false; ++ private void perPlayerMobSpawns() { ++ perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false); ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6124b54d99adbb2a5bb9bb09dfd02522a67ab3ba +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java +@@ -0,0 +1,252 @@ ++package com.destroystokyo.paper.util; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; ++import java.util.List; ++import java.util.Map; ++import net.minecraft.core.SectionPosition; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import org.spigotmc.AsyncCatcher; ++import java.util.HashMap; ++ ++/** @author Spottedleaf */ ++public final class PlayerMobDistanceMap { ++ ++ private static final PooledHashSets.PooledObjectLinkedOpenHashSet EMPTY_SET = new PooledHashSets.PooledObjectLinkedOpenHashSet<>(); ++ ++ private final Map players = new HashMap<>(); ++ // we use linked for better iteration. ++ private final Long2ObjectOpenHashMap> playerMap = new Long2ObjectOpenHashMap<>(32, 0.5f); ++ private int viewDistance; ++ ++ private final PooledHashSets pooledHashSets = new PooledHashSets<>(); ++ ++ public PooledHashSets.PooledObjectLinkedOpenHashSet getPlayersInRange(final ChunkCoordIntPair chunkPos) { ++ return this.getPlayersInRange(chunkPos.x, chunkPos.z); ++ } ++ ++ public PooledHashSets.PooledObjectLinkedOpenHashSet getPlayersInRange(final int chunkX, final int chunkZ) { ++ return this.playerMap.getOrDefault(ChunkCoordIntPair.pair(chunkX, chunkZ), EMPTY_SET); ++ } ++ ++ public void update(final List currentPlayers, final int newViewDistance) { ++ AsyncCatcher.catchOp("Distance map update"); ++ final ObjectLinkedOpenHashSet gone = new ObjectLinkedOpenHashSet<>(this.players.keySet()); ++ ++ final int oldViewDistance = this.viewDistance; ++ this.viewDistance = newViewDistance; ++ ++ for (final EntityPlayer player : currentPlayers) { ++ if (player.isSpectator() || !player.affectsSpawning) { ++ continue; // will be left in 'gone' (or not added at all) ++ } ++ ++ gone.remove(player); ++ ++ final SectionPosition newPosition = player.getPlayerMapSection(); ++ final SectionPosition oldPosition = this.players.put(player, newPosition); ++ ++ if (oldPosition == null) { ++ this.addNewPlayer(player, newPosition, newViewDistance); ++ } else { ++ this.updatePlayer(player, oldPosition, newPosition, oldViewDistance, newViewDistance); ++ } ++ //this.validatePlayer(player, newViewDistance); // debug only ++ } ++ ++ for (final EntityPlayer player : gone) { ++ final SectionPosition oldPosition = this.players.remove(player); ++ if (oldPosition != null) { ++ this.removePlayer(player, oldPosition, oldViewDistance); ++ } ++ } ++ } ++ ++ // expensive op, only for debug ++ private void validatePlayer(final EntityPlayer player, final int viewDistance) { ++ int entiesGot = 0; ++ int expectedEntries = (2 * viewDistance + 1); ++ expectedEntries *= expectedEntries; ++ ++ final SectionPosition currPosition = player.getPlayerMapSection(); ++ ++ final int centerX = currPosition.getX(); ++ final int centerZ = currPosition.getZ(); ++ ++ for (final Long2ObjectLinkedOpenHashMap.Entry> entry : this.playerMap.long2ObjectEntrySet()) { ++ final long key = entry.getLongKey(); ++ final PooledHashSets.PooledObjectLinkedOpenHashSet map = entry.getValue(); ++ ++ if (map.referenceCount == 0) { ++ throw new IllegalStateException("Invalid map"); ++ } ++ ++ if (map.set.contains(player)) { ++ ++entiesGot; ++ ++ final int chunkX = ChunkCoordIntPair.getX(key); ++ final int chunkZ = ChunkCoordIntPair.getZ(key); ++ ++ final int dist = Math.max(Math.abs(chunkX - centerX), Math.abs(chunkZ - centerZ)); ++ ++ if (dist > viewDistance) { ++ throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist); ++ } ++ } ++ } ++ ++ if (entiesGot != expectedEntries) { ++ throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot); ++ } ++ } ++ ++ private void addPlayerTo(final EntityPlayer player, final int chunkX, final int chunkZ) { ++ this.playerMap.compute(ChunkCoordIntPair.pair(chunkX, chunkZ), (final Long key, final PooledHashSets.PooledObjectLinkedOpenHashSet players) -> { ++ if (players == null) { ++ return player.cachedSingleMobDistanceMap; ++ } else { ++ return PlayerMobDistanceMap.this.pooledHashSets.findMapWith(players, player); ++ } ++ }); ++ } ++ ++ private void removePlayerFrom(final EntityPlayer player, final int chunkX, final int chunkZ) { ++ this.playerMap.compute(ChunkCoordIntPair.pair(chunkX, chunkZ), (final Long keyInMap, final PooledHashSets.PooledObjectLinkedOpenHashSet players) -> { ++ return PlayerMobDistanceMap.this.pooledHashSets.findMapWithout(players, player); // rets null instead of an empty map ++ }); ++ } ++ ++ private void updatePlayer(final EntityPlayer player, final SectionPosition oldPosition, final SectionPosition newPosition, final int oldViewDistance, final int newViewDistance) { ++ final int toX = newPosition.getX(); ++ final int toZ = newPosition.getZ(); ++ final int fromX = oldPosition.getX(); ++ final int fromZ = oldPosition.getZ(); ++ ++ final int dx = toX - fromX; ++ final int dz = toZ - fromZ; ++ ++ final int totalX = Math.abs(fromX - toX); ++ final int totalZ = Math.abs(fromZ - toZ); ++ ++ if (Math.max(totalX, totalZ) > (2 * oldViewDistance)) { ++ // teleported? ++ this.removePlayer(player, oldPosition, oldViewDistance); ++ this.addNewPlayer(player, newPosition, newViewDistance); ++ return; ++ } ++ ++ // x axis is width ++ // z axis is height ++ // right refers to the x axis of where we moved ++ // top refers to the z axis of where we moved ++ ++ if (oldViewDistance == newViewDistance) { ++ // same view distance ++ ++ // used for relative positioning ++ final int up = 1 | (dz >> (Integer.SIZE - 1)); // 1 if dz >= 0, -1 otherwise ++ final int right = 1 | (dx >> (Integer.SIZE - 1)); // 1 if dx >= 0, -1 otherwise ++ ++ // The area excluded by overlapping the two view distance squares creates four rectangles: ++ // Two on the left, and two on the right. The ones on the left we consider the "removed" section ++ // and on the right the "added" section. ++ // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually ++ // exclusive to the regions they surround. ++ ++ // 4 points of the rectangle ++ int maxX; // exclusive ++ int minX; // inclusive ++ int maxZ; // exclusive ++ int minZ; // inclusive ++ ++ if (dx != 0) { ++ // handle right addition ++ ++ maxX = toX + (oldViewDistance * right) + right; // exclusive ++ minX = fromX + (oldViewDistance * right) + right; // inclusive ++ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive ++ minZ = toZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.addPlayerTo(player, currX, currZ); ++ } ++ } ++ } ++ ++ if (dz != 0) { ++ // handle up addition ++ ++ maxX = toX + (oldViewDistance * right) + right; // exclusive ++ minX = toX - (oldViewDistance * right); // inclusive ++ maxZ = toZ + (oldViewDistance * up) + up; // exclusive ++ minZ = fromZ + (oldViewDistance * up) + up; // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.addPlayerTo(player, currX, currZ); ++ } ++ } ++ } ++ ++ if (dx != 0) { ++ // handle left removal ++ ++ maxX = toX - (oldViewDistance * right); // exclusive ++ minX = fromX - (oldViewDistance * right); // inclusive ++ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive ++ minZ = toZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.removePlayerFrom(player, currX, currZ); ++ } ++ } ++ } ++ ++ if (dz != 0) { ++ // handle down removal ++ ++ maxX = fromX + (oldViewDistance * right) + right; // exclusive ++ minX = fromX - (oldViewDistance * right); // inclusive ++ maxZ = toZ - (oldViewDistance * up); // exclusive ++ minZ = fromZ - (oldViewDistance * up); // inclusive ++ ++ for (int currX = minX; currX != maxX; currX += right) { ++ for (int currZ = minZ; currZ != maxZ; currZ += up) { ++ this.removePlayerFrom(player, currX, currZ); ++ } ++ } ++ } ++ } else { ++ // different view distance ++ // for now :) ++ this.removePlayer(player, oldPosition, oldViewDistance); ++ this.addNewPlayer(player, newPosition, newViewDistance); ++ } ++ } ++ ++ private void removePlayer(final EntityPlayer player, final SectionPosition position, final int viewDistance) { ++ final int x = position.getX(); ++ final int z = position.getZ(); ++ ++ for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) { ++ for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) { ++ this.removePlayerFrom(player, x + xoff, z + zoff); ++ } ++ } ++ } ++ ++ private void addNewPlayer(final EntityPlayer player, final SectionPosition position, final int viewDistance) { ++ final int x = position.getX(); ++ final int z = position.getZ(); ++ ++ for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) { ++ for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) { ++ this.addPlayerTo(player, x + xoff, z + zoff); ++ } ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4f13d3ff8391793a99f067189f854078334499c6 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java +@@ -0,0 +1,241 @@ ++package com.destroystokyo.paper.util; ++ ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; ++import java.lang.ref.WeakReference; ++import java.util.Iterator; ++ ++/** @author Spottedleaf */ ++public class PooledHashSets { ++ ++ // we really want to avoid that equals() check as much as possible... ++ protected final Object2ObjectOpenHashMap, PooledObjectLinkedOpenHashSet> mapPool = new Object2ObjectOpenHashMap<>(64, 0.25f); ++ ++ protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet current) { ++ if (current.referenceCount == 0) { ++ throw new IllegalStateException("Cannot decrement reference count for " + current); ++ } ++ if (current.referenceCount == -1 || --current.referenceCount > 0) { ++ return; ++ } ++ ++ this.mapPool.remove(current); ++ return; ++ } ++ ++ public PooledObjectLinkedOpenHashSet findMapWith(final PooledObjectLinkedOpenHashSet current, final E object) { ++ final PooledObjectLinkedOpenHashSet cached = current.getAddCache(object); ++ ++ if (cached != null) { ++ if (cached.referenceCount != -1) { ++ ++cached.referenceCount; ++ } ++ ++ decrementReferenceCount(current); ++ ++ return cached; ++ } ++ ++ if (!current.add(object)) { ++ return current; ++ } ++ ++ // we use get/put since we use a different key on put ++ PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); ++ ++ if (ret == null) { ++ ret = new PooledObjectLinkedOpenHashSet<>(current); ++ current.remove(object); ++ this.mapPool.put(ret, ret); ++ ret.referenceCount = 1; ++ } else { ++ if (ret.referenceCount != -1) { ++ ++ret.referenceCount; ++ } ++ current.remove(object); ++ } ++ ++ current.updateAddCache(object, ret); ++ ++ decrementReferenceCount(current); ++ return ret; ++ } ++ ++ // rets null if current.size() == 1 ++ public PooledObjectLinkedOpenHashSet findMapWithout(final PooledObjectLinkedOpenHashSet current, final E object) { ++ if (current.set.size() == 1) { ++ decrementReferenceCount(current); ++ return null; ++ } ++ ++ final PooledObjectLinkedOpenHashSet cached = current.getRemoveCache(object); ++ ++ if (cached != null) { ++ if (cached.referenceCount != -1) { ++ ++cached.referenceCount; ++ } ++ ++ decrementReferenceCount(current); ++ ++ return cached; ++ } ++ ++ if (!current.remove(object)) { ++ return current; ++ } ++ ++ // we use get/put since we use a different key on put ++ PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); ++ ++ if (ret == null) { ++ ret = new PooledObjectLinkedOpenHashSet<>(current); ++ current.add(object); ++ this.mapPool.put(ret, ret); ++ ret.referenceCount = 1; ++ } else { ++ if (ret.referenceCount != -1) { ++ ++ret.referenceCount; ++ } ++ current.add(object); ++ } ++ ++ current.updateRemoveCache(object, ret); ++ ++ decrementReferenceCount(current); ++ return ret; ++ } ++ ++ public static final class PooledObjectLinkedOpenHashSet implements Iterable { ++ ++ private static final WeakReference NULL_REFERENCE = new WeakReference(null); ++ ++ final ObjectLinkedOpenHashSet set; ++ int referenceCount; // -1 if special ++ int hash; // optimize hashcode ++ ++ // add cache ++ WeakReference lastAddObject = NULL_REFERENCE; ++ WeakReference> lastAddMap = NULL_REFERENCE; ++ ++ // remove cache ++ WeakReference lastRemoveObject = NULL_REFERENCE; ++ WeakReference> lastRemoveMap = NULL_REFERENCE; ++ ++ public PooledObjectLinkedOpenHashSet() { ++ this.set = new ObjectLinkedOpenHashSet<>(2, 0.6f); ++ } ++ ++ public PooledObjectLinkedOpenHashSet(final E single) { ++ this(); ++ this.referenceCount = -1; ++ this.add(single); ++ } ++ ++ public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet other) { ++ this.set = other.set.clone(); ++ this.hash = other.hash; ++ } ++ ++ // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java ++ // generated by https://github.com/skeeto/hash-prospector ++ static int hash0(int x) { ++ x *= 0x36935555; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ public PooledObjectLinkedOpenHashSet getAddCache(final E element) { ++ final E currentAdd = this.lastAddObject.get(); ++ ++ if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) { ++ return null; ++ } ++ ++ final PooledObjectLinkedOpenHashSet map = this.lastAddMap.get(); ++ if (map == null || map.referenceCount == 0) { ++ // we need to ret null if ref count is zero as calling code will assume the map is in use ++ return null; ++ } ++ ++ return map; ++ } ++ ++ public PooledObjectLinkedOpenHashSet getRemoveCache(final E element) { ++ final E currentRemove = this.lastRemoveObject.get(); ++ ++ if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) { ++ return null; ++ } ++ ++ final PooledObjectLinkedOpenHashSet map = this.lastRemoveMap.get(); ++ if (map == null || map.referenceCount == 0) { ++ // we need to ret null if ref count is zero as calling code will assume the map is in use ++ return null; ++ } ++ ++ return map; ++ } ++ ++ public void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet map) { ++ this.lastAddObject = new WeakReference<>(element); ++ this.lastAddMap = new WeakReference<>(map); ++ } ++ ++ public void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet map) { ++ this.lastRemoveObject = new WeakReference<>(element); ++ this.lastRemoveMap = new WeakReference<>(map); ++ } ++ ++ boolean add(final E element) { ++ boolean added = this.set.add(element); ++ ++ if (added) { ++ this.hash += hash0(element.hashCode()); ++ } ++ ++ return added; ++ } ++ ++ boolean remove(Object element) { ++ boolean removed = this.set.remove(element); ++ ++ if (removed) { ++ this.hash -= hash0(element.hashCode()); ++ } ++ ++ return removed; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return this.set.iterator(); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.hash; ++ } ++ ++ @Override ++ public boolean equals(final Object other) { ++ if (!(other instanceof PooledObjectLinkedOpenHashSet)) { ++ return false; ++ } ++ if (this.referenceCount == 0) { ++ return other == this; ++ } else { ++ if (other == this) { ++ // Unfortunately we are never equal to our own instance while in use! ++ return false; ++ } ++ return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set); ++ } ++ } ++ ++ @Override ++ public String toString() { ++ return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " + ++ this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString(); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index e108705575669a97fffd56f3c215fde09b0f180a..b971082b76918fdff3bc7ebbdf6d426b04b69b26 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -768,7 +768,22 @@ public class ChunkProviderServer extends IChunkProvider { + this.world.getMethodProfiler().enter("naturalSpawnCount"); + this.world.timings.countNaturalMobs.startTiming(); // Paper - timings + int l = this.chunkMapDistance.b(); +- SpawnerCreature.d spawnercreature_d = SpawnerCreature.a(l, this.world.A(), this::a); ++ // Paper start - per player mob spawning ++ SpawnerCreature.d spawnercreature_d; // moved down ++ if (this.playerChunkMap.playerMobDistanceMap != null) { ++ // update distance map ++ this.world.timings.playerMobDistanceMapUpdate.startTiming(); ++ this.playerChunkMap.playerMobDistanceMap.update(this.world.players, this.playerChunkMap.viewDistance); ++ this.world.timings.playerMobDistanceMapUpdate.stopTiming(); ++ // re-set mob counts ++ for (EntityPlayer player : this.world.players) { ++ Arrays.fill(player.mobCounts, 0); ++ } ++ spawnercreature_d = SpawnerCreature.countMobs(l, this.world.A(), this::a, true); ++ } else { ++ spawnercreature_d = SpawnerCreature.countMobs(l, this.world.A(), this::a, false); ++ } ++ // Paper end + this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings + + this.p = spawnercreature_d; +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 49e3205dbd94b06b9504039c1a93f830b025a8bb..1e882d9d9b797bb5fb0411f5ecdedf01bcfe5aca 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -94,6 +94,7 @@ import net.minecraft.world.effect.MobEffects; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityInsentient; + import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EnumCreatureType; + import net.minecraft.world.entity.EnumMainHand; + import net.minecraft.world.entity.IEntityAngerable; + import net.minecraft.world.entity.animal.horse.EntityHorseAbstract; +@@ -218,6 +219,11 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public boolean queueHealthUpdatePacket = false; + public net.minecraft.network.protocol.game.PacketPlayOutUpdateHealth queuedHealthUpdatePacket; + // Paper end ++ // Paper start - mob spawning rework ++ public static final int ENUMCREATURETYPE_TOTAL_ENUMS = EnumCreatureType.values().length; ++ public final int[] mobCounts = new int[ENUMCREATURETYPE_TOTAL_ENUMS]; // Paper ++ public final com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet cachedSingleMobDistanceMap; ++ // Paper end + + // CraftBukkit start + public String displayName; +@@ -256,6 +262,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getName()); // Paper + this.canPickUpLoot = true; + this.maxHealthCache = this.getMaxHealth(); ++ this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper + } + + // Yes, this doesn't match Vanilla, but it's the best we can do for now. +@@ -2052,6 +2059,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + } + ++ public final SectionPosition getPlayerMapSection() { return this.O(); } // Paper - OBFHELPER + public SectionPosition O() { + return this.cj; + } +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index a6c3bed5824d112042536a5666098d4d80173c3b..9c5b1dd305567f09a23a3f189d4dadba323b643e 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -72,6 +72,7 @@ import net.minecraft.util.thread.ThreadedMailbox; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityInsentient; + import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.EnumCreatureType; + import net.minecraft.world.entity.ai.village.poi.VillagePlace; + import net.minecraft.world.entity.boss.EntityComplexPart; + import net.minecraft.world.entity.player.EntityHuman; +@@ -129,7 +130,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + public final Int2ObjectMap trackedEntities; + private final Long2ByteMap z; + private final Queue A; private final Queue getUnloadQueueTasks() { return this.A; } // Paper - OBFHELPER +- private int viewDistance; ++ int viewDistance; // Paper - private -> package private ++ public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper + + // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() + public final CallbackExecutor callbackExecutor = new CallbackExecutor(); +@@ -208,6 +210,24 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.l = supplier; + this.m = new VillagePlace(new File(this.w, "poi"), datafixer, flag, this.world); // Paper + this.setViewDistance(i); ++ this.playerMobDistanceMap = this.world.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper ++ } ++ ++ public void updatePlayerMobTypeMap(Entity entity) { ++ if (!this.world.paperConfig.perPlayerMobSpawns) { ++ return; ++ } ++ int chunkX = (int)Math.floor(entity.locX()) >> 4; ++ int chunkZ = (int)Math.floor(entity.locZ()) >> 4; ++ int index = entity.getEntityType().getEnumCreatureType().ordinal(); ++ ++ for (EntityPlayer player : this.playerMobDistanceMap.getPlayersInRange(chunkX, chunkZ)) { ++ ++player.mobCounts[index]; ++ } ++ } ++ ++ public int getMobCountNear(EntityPlayer entityPlayer, EnumCreatureType enumCreatureType) { ++ return entityPlayer.mobCounts[enumCreatureType.ordinal()]; + } + + private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) { +diff --git a/src/main/java/net/minecraft/world/entity/EntityTypes.java b/src/main/java/net/minecraft/world/entity/EntityTypes.java +index 1355c074353611669c947cb0f06c67be0ab418aa..9d2955f05aadd4bbc6dcfec068a55d7fe6950ba0 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityTypes.java ++++ b/src/main/java/net/minecraft/world/entity/EntityTypes.java +@@ -426,6 +426,7 @@ public class EntityTypes { + return this.bl; + } + ++ public final EnumCreatureType getEnumCreatureType() { return this.e(); } // Paper - OBFHELPER + public EnumCreatureType e() { + return this.bg; + } +diff --git a/src/main/java/net/minecraft/world/level/SpawnerCreature.java b/src/main/java/net/minecraft/world/level/SpawnerCreature.java +index d30a3de84dc75a57680052904337af02b6b80636..24771c3522ea74ac12058591137eafc21adf3762 100644 +--- a/src/main/java/net/minecraft/world/level/SpawnerCreature.java ++++ b/src/main/java/net/minecraft/world/level/SpawnerCreature.java +@@ -16,6 +16,7 @@ import net.minecraft.core.IPosition; + import net.minecraft.core.IRegistry; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.server.MCUtil; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.WorldServer; + import net.minecraft.tags.Tag; + import net.minecraft.tags.TagsBlock; +@@ -62,6 +63,11 @@ public final class SpawnerCreature { + }); + + public static SpawnerCreature.d a(int i, Iterable iterable, SpawnerCreature.b spawnercreature_b) { ++ // Paper start - add countMobs parameter ++ return countMobs(i, iterable, spawnercreature_b, false); ++ } ++ public static SpawnerCreature.d countMobs(int i, Iterable iterable, SpawnerCreature.b spawnercreature_b, boolean countMobs) { ++ // Paper end - add countMobs parameter + SpawnerCreatureProbabilities spawnercreatureprobabilities = new SpawnerCreatureProbabilities(); + Object2IntOpenHashMap object2intopenhashmap = new Object2IntOpenHashMap(); + Iterator iterator = iterable.iterator(); +@@ -99,6 +105,11 @@ public final class SpawnerCreature { + } + + object2intopenhashmap.addTo(enumcreaturetype, 1); ++ // Paper start ++ if (countMobs) { ++ ((WorldServer)chunk.world).getChunkProvider().playerChunkMap.updatePlayerMobTypeMap(entity); ++ } ++ // Paper end + }); + } + } +@@ -157,13 +168,31 @@ public final class SpawnerCreature { + continue; + } + +- if ((flag || !enumcreaturetype.d()) && (flag1 || enumcreaturetype.d()) && (flag2 || !enumcreaturetype.e()) && spawnercreature_d.a(enumcreaturetype, limit)) { ++ // Paper start - only allow spawns upto the limit per chunk and update count afterwards ++ int currEntityCount = spawnercreature_d.getEntityCountsByType().getInt(enumcreaturetype); ++ int k1 = limit * spawnercreature_d.getSpawnerChunks() / SpawnerCreature.b; ++ int difference = k1 - currEntityCount; ++ ++ if (worldserver.paperConfig.perPlayerMobSpawns) { ++ int minDiff = Integer.MAX_VALUE; ++ for (EntityPlayer entityplayer : worldserver.getChunkProvider().playerChunkMap.playerMobDistanceMap.getPlayersInRange(chunk.getPos())) { ++ minDiff = Math.min(limit - worldserver.getChunkProvider().playerChunkMap.getMobCountNear(entityplayer, enumcreaturetype), minDiff); ++ } ++ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; ++ } ++ // Paper end ++ ++ // Paper start - per player mob spawning ++ if ((flag || !enumcreaturetype.d()) && (flag1 || enumcreaturetype.d()) && (flag2 || !enumcreaturetype.e()) && difference > 0) { + // CraftBukkit end +- a(enumcreaturetype, worldserver, chunk, (entitytypes, blockposition, ichunkaccess) -> { ++ int spawnCount = spawnMobs(enumcreaturetype, worldserver, chunk, (entitytypes, blockposition, ichunkaccess) -> { + return spawnercreature_d.a(entitytypes, blockposition, ichunkaccess); + }, (entityinsentient, ichunkaccess) -> { + spawnercreature_d.a(entityinsentient, ichunkaccess); +- }); ++ }, ++ difference, worldserver.paperConfig.perPlayerMobSpawns ? worldserver.getChunkProvider().playerChunkMap::updatePlayerMobTypeMap : null); ++ spawnercreature_d.getEntityCountsByType().mergeInt(enumcreaturetype, spawnCount, Integer::sum); ++ // Paper end - per player mob spawning + } + } + +@@ -172,22 +201,34 @@ public final class SpawnerCreature { + } + + public static void a(EnumCreatureType enumcreaturetype, WorldServer worldserver, Chunk chunk, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a) { ++ // Paper start - add parameters and int ret type ++ spawnMobs(enumcreaturetype, worldserver, chunk, spawnercreature_c, spawnercreature_a, Integer.MAX_VALUE, null); ++ } ++ public static int spawnMobs(EnumCreatureType enumcreaturetype, WorldServer worldserver, Chunk chunk, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a, int maxSpawns, Consumer trackEntity) { ++ // Paper end - add parameters and int ret type + BlockPosition blockposition = getRandomPosition(worldserver, chunk); + + if (blockposition.getY() >= 1) { +- a(enumcreaturetype, worldserver, (IChunkAccess) chunk, blockposition, spawnercreature_c, spawnercreature_a); ++ return spawnMobsInternal(enumcreaturetype, worldserver, (IChunkAccess) chunk, blockposition, spawnercreature_c, spawnercreature_a, maxSpawns, trackEntity); + } ++ return 0; // Paper + } + + public static void a(EnumCreatureType enumcreaturetype, WorldServer worldserver, IChunkAccess ichunkaccess, BlockPosition blockposition, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a) { ++ // Paper start - add maxSpawns parameter and return spawned mobs ++ spawnMobsInternal(enumcreaturetype, worldserver, ichunkaccess, blockposition, spawnercreature_c, spawnercreature_a, Integer.MAX_VALUE, null); ++ } ++ public static int spawnMobsInternal(EnumCreatureType enumcreaturetype, WorldServer worldserver, IChunkAccess ichunkaccess, BlockPosition blockposition, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a, int maxSpawns, Consumer trackEntity) { ++ // Paper end - add maxSpawns parameter and return spawned mobs + StructureManager structuremanager = worldserver.getStructureManager(); + ChunkGenerator chunkgenerator = worldserver.getChunkProvider().getChunkGenerator(); + int i = blockposition.getY(); + IBlockData iblockdata = worldserver.getTypeIfLoadedAndInBounds(blockposition); // Paper - don't load chunks for mob spawn ++ int j = 0; // Paper - moved up + + if (iblockdata != null && !iblockdata.isOccluding(ichunkaccess, blockposition)) { // Paper - don't load chunks for mob spawn + BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); +- int j = 0; ++ // Paper - moved up + int k = 0; + + while (k < 3) { +@@ -227,7 +268,7 @@ public final class SpawnerCreature { + // Paper start + Boolean doSpawning = a(worldserver, enumcreaturetype, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); + if (doSpawning == null) { +- return; ++ return j; // Paper + } + if (doSpawning && spawnercreature_c.test(biomesettingsmobs_c.c, blockposition_mutableblockposition, ichunkaccess)) { + // Paper end +@@ -235,7 +276,7 @@ public final class SpawnerCreature { + + + if (entityinsentient == null) { +- return; ++ return j; // Paper + } + + entityinsentient.setPositionRotation(d0, (double) i, d1, worldserver.random.nextFloat() * 360.0F, 0.0F); +@@ -244,13 +285,18 @@ public final class SpawnerCreature { + // CraftBukkit start + worldserver.addAllEntities(entityinsentient, SpawnReason.NATURAL); + if (!entityinsentient.dead) { +- ++j; ++ ++j; // Paper - force diff on name change - we expect this to be the total amount spawned + ++k1; + spawnercreature_a.run(entityinsentient, ichunkaccess); ++ // Paper start ++ if (trackEntity != null) { ++ trackEntity.accept(entityinsentient); ++ } ++ // Paper end + } + // CraftBukkit end +- if (j >= entityinsentient.getMaxSpawnGroup()) { +- return; ++ if (j >= entityinsentient.getMaxSpawnGroup() || j >= maxSpawns) { // Paper ++ return j; // Paper + } + + if (entityinsentient.c(k1)) { +@@ -272,6 +318,7 @@ public final class SpawnerCreature { + } + + } ++ return j; // Paper + } + + private static boolean a(WorldServer worldserver, IChunkAccess ichunkaccess, BlockPosition.MutableBlockPosition blockposition_mutableblockposition, double d0) { +@@ -512,8 +559,8 @@ public final class SpawnerCreature { + + public static class d { + +- private final int a; +- private final Object2IntOpenHashMap b; ++ private final int a; final int getSpawnerChunks() { return this.a; } // Paper - OBFHELPER ++ private final Object2IntOpenHashMap b; final Object2IntMap getEntityCountsByType() { return this.b; } // Paper - OBFHELPER + private final SpawnerCreatureProbabilities c; + private final Object2IntMap d; + @Nullable +@@ -574,7 +621,7 @@ public final class SpawnerCreature { + + // CraftBukkit start + private boolean a(EnumCreatureType enumcreaturetype, int limit) { +- int i = limit * this.a / SpawnerCreature.b; ++ int i = limit * this.a / SpawnerCreature.b; // Paper - diff on change, needed in the spawn method + // CraftBukkit end + + return this.b.getInt(enumcreaturetype) < i; diff --git a/patches/server-unmapped/0001/0377-Prevent-consuming-the-wrong-itemstack.patch b/patches/server-unmapped/0001/0377-Prevent-consuming-the-wrong-itemstack.patch new file mode 100644 index 0000000000..0f8ff9bd80 --- /dev/null +++ b/patches/server-unmapped/0001/0377-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/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index c98562980ca02c85f2f777a31983c164f2dd5e1e..06119a87338d48128ddd71fe8396e10b6e83744a 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -3201,10 +3201,13 @@ public abstract class EntityLiving extends Entity { + this.datawatcher.set(EntityLiving.ag, (byte) j); + } + +- public void c(EnumHand enumhand) { ++ // Paper start -- OBFHELPER and forwarder to method with forceUpdate parameter ++ public void c(EnumHand enumhand) { this.updateActiveItem(enumhand, false); } ++ public void updateActiveItem(EnumHand enumhand, boolean forceUpdate) { ++ // Paper end + ItemStack itemstack = this.b(enumhand); + +- if (!itemstack.isEmpty() && !this.isHandRaised()) { ++ if (!itemstack.isEmpty() && !this.isHandRaised() || forceUpdate) { // Paper use override flag + this.activeItem = itemstack; + this.bd = itemstack.k(); + if (!this.world.isClientSide) { +@@ -3282,6 +3285,7 @@ public abstract class EntityLiving extends Entity { + this.releaseActiveItem(); + } else { + if (!this.activeItem.isEmpty() && this.isHandRaised()) { ++ this.updateActiveItem(this.getRaisedHand(), true); // Paper + this.b(this.activeItem, 16); + // CraftBukkit start - fire PlayerItemConsumeEvent + ItemStack itemstack; +@@ -3316,8 +3320,8 @@ public abstract class EntityLiving extends Entity { + } + + this.clearActiveItem(); +- // Paper start - if the replacement is anything but the default, update the client inventory +- if (this instanceof EntityPlayer && !com.google.common.base.Objects.equal(defaultReplacement, itemstack)) { ++ // Paper start ++ if (this instanceof EntityPlayer) { + ((EntityPlayer) this).getBukkitEntity().updateInventory(); + } + // Paper end diff --git a/patches/server-unmapped/0001/0378-Fix-nether-portal-creation.patch b/patches/server-unmapped/0001/0378-Fix-nether-portal-creation.patch new file mode 100644 index 0000000000..8f4f9520f4 --- /dev/null +++ b/patches/server-unmapped/0001/0378-Fix-nether-portal-creation.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Michael Himing +Date: Mon, 9 Sep 2019 13:21:17 +1000 +Subject: [PATCH] Fix nether portal creation + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java +index 8f77cf482df0208b16a172359c8c192e0b701830..26d63e20435e3a0111224acfc7dfce739a87cf6f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java +@@ -38,6 +38,11 @@ public class BlockStateListPopulator extends DummyGeneratorAccess { + + @Override + public boolean setTypeAndData(BlockPosition position, IBlockData data, int flag) { ++ // Paper start ++ // When a LinkedHashMap entry is overwritten, it keeps its old position. Removing the entry here before adding ++ // a new one ensures that the nether portal blocks are placed last and are not destroyed by physics. ++ list.remove(position); ++ // Paper end + CraftBlockState state = CraftBlockState.getBlockState(world, position, flag); + state.setData(data); + list.put(position, state); diff --git a/patches/server-unmapped/0001/0379-Generator-Settings.patch b/patches/server-unmapped/0001/0379-Generator-Settings.patch new file mode 100644 index 0000000000..35a2520f5f --- /dev/null +++ b/patches/server-unmapped/0001/0379-Generator-Settings.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Wed, 2 Mar 2016 02:17:54 -0600 +Subject: [PATCH] Generator Settings + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 6aec502eb529d4090306e12e837117cde7e114eb..290e49cf0077909ad7ab8127c01ef93cf7b70b51 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -570,4 +570,9 @@ public class PaperWorldConfig { + private void perPlayerMobSpawns() { + perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false); + } ++ ++ public boolean generateFlatBedrock; ++ private void generatorSettings() { ++ generateFlatBedrock = getBoolean("generator-settings.flat-bedrock", false); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/IChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/IChunkAccess.java +index e570dc58efa56bd0aa5ada5575b4054ee38d505e..cdf612d7553a8f4aaebb5e0e66bd2a47a280457a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/IChunkAccess.java ++++ b/src/main/java/net/minecraft/world/level/chunk/IChunkAccess.java +@@ -25,6 +25,18 @@ import org.apache.logging.log4j.LogManager; + + public interface IChunkAccess extends IBlockAccess, IStructureAccess { + ++ // Paper start ++ default boolean generateFlatBedrock() { ++ if (this instanceof ProtoChunk) { ++ return ((ProtoChunk)this).world.paperConfig.generateFlatBedrock; ++ } else if (this instanceof Chunk) { ++ return ((Chunk)this).world.paperConfig.generateFlatBedrock; ++ } else { ++ return false; ++ } ++ } ++ // Paper end ++ + IBlockData getType(final int x, final int y, final int z); // Paper + @Nullable + IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag); +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 9b308a10554b037ede0c455fbd3e906021218ddc..7bfac4e852c4a6697435647dab173913df6034e9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -64,7 +64,7 @@ public class ProtoChunk implements IChunkAccess { + private long s; + private final Map t; + private volatile boolean u; +- private final World world; // Paper - Anti-Xray - Add world ++ final World world; // Paper - Anti-Xray - Add world // Paper - private -> default + + // Paper start - Anti-Xray - Add world + @Deprecated public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter) { this(chunkcoordintpair, chunkconverter, null); } // Notice for updates: Please make sure this constructor isn't used anywhere +diff --git a/src/main/java/net/minecraft/world/level/levelgen/ChunkGeneratorAbstract.java b/src/main/java/net/minecraft/world/level/levelgen/ChunkGeneratorAbstract.java +index b137d65953fe1e44709e9a6dab3a4533df644d06..700b32322e8d0fbb8ec2824e50a340be16b48f81 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/ChunkGeneratorAbstract.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/ChunkGeneratorAbstract.java +@@ -408,8 +408,8 @@ public final class ChunkGeneratorAbstract extends ChunkGenerator { + int i = ichunkaccess.getPos().d(); + int j = ichunkaccess.getPos().e(); + GeneratorSettingBase generatorsettingbase = (GeneratorSettingBase) this.h.get(); +- int k = generatorsettingbase.f(); +- int l = this.x - 1 - generatorsettingbase.e(); ++ int k = generatorsettingbase.f(); final int floorHeight = k; // Paper ++ int l = this.x - 1 - generatorsettingbase.e(); final int roofHeight = l; // Paper + boolean flag = true; + boolean flag1 = l + 4 >= 0 && l < this.x; + boolean flag2 = k + 4 >= 0 && k < this.x; +@@ -423,7 +423,7 @@ public final class ChunkGeneratorAbstract extends ChunkGenerator { + + if (flag1) { + for (i1 = 0; i1 < 5; ++i1) { +- if (i1 <= random.nextInt(5)) { ++ if (i1 <= (ichunkaccess.generateFlatBedrock() ? roofHeight : random.nextInt(5))) { // Paper - Configurable flat bedrock roof + ichunkaccess.setType(blockposition_mutableblockposition.d(blockposition.getX(), l - i1, blockposition.getZ()), Blocks.BEDROCK.getBlockData(), false); + } + } +@@ -431,7 +431,7 @@ public final class ChunkGeneratorAbstract extends ChunkGenerator { + + if (flag2) { + for (i1 = 4; i1 >= 0; --i1) { +- if (i1 <= random.nextInt(5)) { ++ if (i1 <= (ichunkaccess.generateFlatBedrock() ? floorHeight : random.nextInt(5))) { // Paper - Configurable flat bedrock floor + ichunkaccess.setType(blockposition_mutableblockposition.d(blockposition.getX(), k + i1, blockposition.getZ()), Blocks.BEDROCK.getBlockData(), false); + } + } diff --git a/patches/server-unmapped/0001/0380-Fix-MC-161754.patch b/patches/server-unmapped/0001/0380-Fix-MC-161754.patch new file mode 100644 index 0000000000..8ef2ece0bd --- /dev/null +++ b/patches/server-unmapped/0001/0380-Fix-MC-161754.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 24 Sep 2019 16:03:00 -0700 +Subject: [PATCH] Fix MC-161754 + +Fixes https://github.com/PaperMC/Paper/issues/2580 + +We can use an entity valid check since this method is invoked for +each inventory iteraction (thanks to CB) and on player tick (vanilla). + +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerHorse.java b/src/main/java/net/minecraft/world/inventory/ContainerHorse.java +index ecabe8e52865b71b6f89d09850b37741e7e79b50..ea64ef313a8378fa7dee086e137e1e5f43376804 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerHorse.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerHorse.java +@@ -85,7 +85,7 @@ public class ContainerHorse extends Container { + + @Override + public boolean canUse(EntityHuman entityhuman) { +- return this.c.a(entityhuman) && this.d.isAlive() && this.d.g((Entity) entityhuman) < 8.0F; ++ return this.c.a(entityhuman) && (this.d.isAlive() && this.d.valid) && this.d.g((Entity) entityhuman) < 8.0F; // Paper - Fix MC-161754 + } + + @Override diff --git a/patches/server-unmapped/0001/0381-Performance-improvement-for-Chunk.getEntities.patch b/patches/server-unmapped/0001/0381-Performance-improvement-for-Chunk.getEntities.patch new file mode 100644 index 0000000000..0b09feb060 --- /dev/null +++ b/patches/server-unmapped/0001/0381-Performance-improvement-for-Chunk.getEntities.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wea_ondara +Date: Thu, 10 Oct 2019 11:29:42 +0200 +Subject: [PATCH] Performance improvement for Chunk.getEntities + +This patch aims to reduce performance cost used by collecting the +entities of a chunk. Previously the entitySlices were copied into an +extra array with List.toArray() with is a costly and unneccessary +operation. This patch will reduce the load of plugins which for example +implement custom moblimits and depend on Chunk.getEntities(). + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index c36f55f178166eb099cc5c64784be5a9f4750199..8ade81a693286cdf65f8c0eeca2121a217c90350 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -117,14 +117,14 @@ public class CraftChunk implements Chunk { + Entity[] entities = new Entity[count]; + + for (int i = 0; i < 16; i++) { +- +- for (Object obj : chunk.entitySlices[i].toArray()) { +- if (!(obj instanceof net.minecraft.world.entity.Entity)) { ++ // Paper start - speed up (was with chunk.entitySlices[i].toArray() and cast checks which costs a lot of performance if called often) ++ for (net.minecraft.world.entity.Entity entity : chunk.entitySlices[i]) { ++ if (entity == null) { + continue; + } +- +- entities[index++] = ((net.minecraft.world.entity.Entity) obj).getBukkitEntity(); ++ entities[index++] = entity.getBukkitEntity(); + } ++ // Paper end + } + + return entities; diff --git a/patches/server-unmapped/0001/0382-Fix-spawning-of-hanging-entities-that-are-not-ItemFr.patch b/patches/server-unmapped/0001/0382-Fix-spawning-of-hanging-entities-that-are-not-ItemFr.patch new file mode 100644 index 0000000000..84c4e83f51 --- /dev/null +++ b/patches/server-unmapped/0001/0382-Fix-spawning-of-hanging-entities-that-are-not-ItemFr.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MisterErwin +Date: Wed, 30 Oct 2019 16:57:54 +0100 +Subject: [PATCH] Fix spawning of hanging entities that are not ItemFrames and + can not face UP or DOWN + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index e880ce299bd81ad790da7b94fb28c49a5ebab1e7..5a7efa46729a233d3aa8b674b1f6a8043b6c5632 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1886,7 +1886,12 @@ public class CraftWorld implements World { + height = 9; + } + +- BlockFace[] faces = new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH, BlockFace.UP, BlockFace.DOWN}; ++ // Paper start - In addition to d65a2576e40e58c8e446b330febe6799d13a604f do not check UP/DOWN for non item frames ++ // BlockFace[] faces = new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH, BlockFace.UP, BlockFace.DOWN}; ++ BlockFace[] faces = (ItemFrame.class.isAssignableFrom(clazz)) ++ ? new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH, BlockFace.UP, BlockFace.DOWN} ++ : new BlockFace[]{BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH}; ++ // Paper end + final BlockPosition pos = new BlockPosition(x, y, z); + for (BlockFace dir : faces) { + IBlockData nmsBlock = world.getType(pos.shift(CraftBlock.blockFaceToNotch(dir))); diff --git a/patches/server-unmapped/0001/0383-Expose-the-internal-current-tick.patch b/patches/server-unmapped/0001/0383-Expose-the-internal-current-tick.patch new file mode 100644 index 0000000000..a98f6a1dba --- /dev/null +++ b/patches/server-unmapped/0001/0383-Expose-the-internal-current-tick.patch @@ -0,0 +1,21 @@ +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 153de70249442caa3568fb2591cfd88bdb37e687..cfacac7233c6e1f1a34aee1b5d26a072949fd512 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2358,5 +2358,10 @@ public final class CraftServer implements Server { + } + return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name); + } ++ ++ @Override ++ public int getCurrentTick() { ++ return net.minecraft.server.MinecraftServer.currentTick; ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0384-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch b/patches/server-unmapped/0001/0384-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch new file mode 100644 index 0000000000..51a0ffb5c2 --- /dev/null +++ b/patches/server-unmapped/0001/0384-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 9 Oct 2019 21:51:43 -0500 +Subject: [PATCH] Fix stuck in sneak when changing worlds (MC-10657) + + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 1e882d9d9b797bb5fb0411f5ecdedf01bcfe5aca..23bb5cd0b10c211019ff0b71128bbf835238e9d8 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -1072,6 +1072,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.lastHealthSent = -1.0F; + this.lastFoodSent = -1; + ++ setSneaking(false); // Paper - fix MC-10657 ++ + // CraftBukkit start + PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld()); + this.world.getServer().getPluginManager().callEvent(changeEvent); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 54e49dcc56281d02203fdcdb20906a4ee0e43c05..c5116a9c596074a33c98d29bb1e9cf22a8ad53bf 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -846,6 +846,8 @@ public abstract class PlayerList { + entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityEffect(entityplayer.getId(), mobEffect)); + } + ++ entityplayer.setSneaking(false); // Paper - fix MC-10657 ++ + // Fire advancement trigger + entityplayer.triggerDimensionAdvancements(((CraftWorld) fromWorld).getHandle()); + diff --git a/patches/server-unmapped/0001/0385-Add-option-to-disable-pillager-patrols.patch b/patches/server-unmapped/0001/0385-Add-option-to-disable-pillager-patrols.patch new file mode 100644 index 0000000000..7adaa55dec --- /dev/null +++ b/patches/server-unmapped/0001/0385-Add-option-to-disable-pillager-patrols.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 9 Oct 2019 21:46:15 -0500 +Subject: [PATCH] Add option to disable pillager patrols + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 290e49cf0077909ad7ab8127c01ef93cf7b70b51..e726b6213cf2e8f5b326f05c0438b8f1ee2b73c5 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -575,4 +575,9 @@ public class PaperWorldConfig { + private void generatorSettings() { + generateFlatBedrock = getBoolean("generator-settings.flat-bedrock", false); + } ++ ++ public boolean disablePillagerPatrols = false; ++ private void pillagerSettings() { ++ disablePillagerPatrols = getBoolean("game-mechanics.disable-pillager-patrols", disablePillagerPatrols); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPatrol.java b/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPatrol.java +index 04a1af9c6742f7aa944dec80e75ff8a4ca4bf57f..cba98adb7f2711fb97c7e4120d962f46a59682e7 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPatrol.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPatrol.java +@@ -26,6 +26,7 @@ public class MobSpawnerPatrol implements MobSpawner { + + @Override + public int a(WorldServer worldserver, boolean flag, boolean flag1) { ++ if (worldserver.paperConfig.disablePillagerPatrols) return 0; // Paper + if (!flag) { + return 0; + } else if (!worldserver.getGameRules().getBoolean(GameRules.DO_PATROL_SPAWNING)) { diff --git a/patches/server-unmapped/0001/0386-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch b/patches/server-unmapped/0001/0386-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch new file mode 100644 index 0000000000..b2d5dc3587 --- /dev/null +++ b/patches/server-unmapped/0001/0386-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lukasz Derlatka +Date: Mon, 11 Nov 2019 16:08:13 +0100 +Subject: [PATCH] Fix AssertionError when player hand set to empty type + +Fixes an AssertionError when setting the player's item in hand to null or a new ItemStack of Air in PlayerInteractEvent +Fixes GH-2718 + +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index eee5b3e4645ae41f63aba8898c58f43402d31b73..cfdfa3ea95a525af25c7aa830f8e31d5afe56d65 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1707,6 +1707,10 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524 + return; + } ++ // Paper start ++ itemstack = this.player.getItemInHand(enumhand); ++ if (itemstack.isEmpty()) return; ++ // Paper end + EnumInteractionResult enuminteractionresult = this.player.playerInteractManager.a(this.player, worldserver, itemstack, enumhand); + + if (enuminteractionresult.b()) { +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 06119a87338d48128ddd71fe8396e10b6e83744a..ec31585099c8376f61496f02f9454cb600104918 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -2156,6 +2156,7 @@ public abstract class EntityLiving extends Entity { + return predicate.test(this.getItemInMainHand().getItem()) || predicate.test(this.getItemInOffHand().getItem()); + } + ++ public final ItemStack getItemInHand(EnumHand enumhand) { return this.b(enumhand); } // Paper - OBFHELPER + public ItemStack b(EnumHand enumhand) { + if (enumhand == EnumHand.MAIN_HAND) { + return this.getEquipment(EnumItemSlot.MAINHAND); diff --git a/patches/server-unmapped/0001/0387-PlayerLaunchProjectileEvent.patch b/patches/server-unmapped/0001/0387-PlayerLaunchProjectileEvent.patch new file mode 100644 index 0000000000..ceeb9bcd0e --- /dev/null +++ b/patches/server-unmapped/0001/0387-PlayerLaunchProjectileEvent.patch @@ -0,0 +1,329 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 21 Jul 2018 03:11:03 -0500 +Subject: [PATCH] PlayerLaunchProjectileEvent + + +diff --git a/src/main/java/net/minecraft/world/InteractionResultWrapper.java b/src/main/java/net/minecraft/world/InteractionResultWrapper.java +index dd17c111670e637b574f5c7f38d27848900ce194..8cecc3d909a51b1892b4a299a5e6ec3518db9b39 100644 +--- a/src/main/java/net/minecraft/world/InteractionResultWrapper.java ++++ b/src/main/java/net/minecraft/world/InteractionResultWrapper.java +@@ -10,6 +10,7 @@ public class InteractionResultWrapper { + this.b = t0; + } + ++ public EnumInteractionResult getResult() { return this.a(); } // Paper - OBFHELPER + public EnumInteractionResult a() { + return this.a; + } +diff --git a/src/main/java/net/minecraft/world/item/ItemEgg.java b/src/main/java/net/minecraft/world/item/ItemEgg.java +index 2083ab6e0dc7e48d409a5ee33e712e34abd6f6bf..4b1a6ee784da4595931396a905f1358b7a13f3dd 100644 +--- a/src/main/java/net/minecraft/world/item/ItemEgg.java ++++ b/src/main/java/net/minecraft/world/item/ItemEgg.java +@@ -25,21 +25,35 @@ public class ItemEgg extends Item { + + entityegg.setItem(itemstack); + entityegg.a(entityhuman, entityhuman.pitch, entityhuman.yaw, 0.0F, 1.5F, 1.0F); +- // CraftBukkit start +- if (!world.addEntity(entityegg)) { ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityegg.getBukkitEntity()); ++ if (event.callEvent() && world.addEntity(entityegg)) { ++ if (event.shouldConsume() && !entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } else if (entityhuman instanceof net.minecraft.server.level.EntityPlayer) { ++ ((net.minecraft.server.level.EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); ++ } ++ ++ world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_EGG_THROW, SoundCategory.PLAYERS, 0.5F, 0.4F / (net.minecraft.world.entity.Entity.SHARED_RANDOM.nextFloat() * 0.4F + 0.8F)); ++ entityhuman.b(StatisticList.ITEM_USED.b(this)); ++ } else { + if (entityhuman instanceof net.minecraft.server.level.EntityPlayer) { + ((net.minecraft.server.level.EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); + } +- return InteractionResultWrapper.fail(itemstack); ++ return new InteractionResultWrapper(net.minecraft.world.EnumInteractionResult.FAIL, itemstack); + } +- // CraftBukkit end ++ // Paper end ++ ++ + } + world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_EGG_THROW, SoundCategory.PLAYERS, 0.5F, 0.4F / (ItemEgg.RANDOM.nextFloat() * 0.4F + 0.8F)); // CraftBukkit - from above + ++ /* // Paper start - moved up + entityhuman.b(StatisticList.ITEM_USED.b(this)); + if (!entityhuman.abilities.canInstantlyBuild) { + itemstack.subtract(1); + } ++ */ // Paper end + + return InteractionResultWrapper.a(itemstack, world.s_()); + } +diff --git a/src/main/java/net/minecraft/world/item/ItemEnderPearl.java b/src/main/java/net/minecraft/world/item/ItemEnderPearl.java +index 5349282b9a5b43c4c3539e1677971463e2ca5a17..9896d77381e7fadf1ef2619210713e190c1445d0 100644 +--- a/src/main/java/net/minecraft/world/item/ItemEnderPearl.java ++++ b/src/main/java/net/minecraft/world/item/ItemEnderPearl.java +@@ -25,22 +25,37 @@ public class ItemEnderPearl extends Item { + + entityenderpearl.setItem(itemstack); + entityenderpearl.a(entityhuman, entityhuman.pitch, entityhuman.yaw, 0.0F, 1.5F, 1.0F); +- if (!world.addEntity(entityenderpearl)) { ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityenderpearl.getBukkitEntity()); ++ if (event.callEvent() && world.addEntity(entityenderpearl)) { ++ if (event.shouldConsume() && !entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } else if (entityhuman instanceof net.minecraft.server.level.EntityPlayer) { ++ ((net.minecraft.server.level.EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); ++ } ++ ++ world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_ENDER_PEARL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (net.minecraft.world.entity.Entity.SHARED_RANDOM.nextFloat() * 0.4F + 0.8F)); ++ entityhuman.b(StatisticList.ITEM_USED.b(this)); ++ entityhuman.getCooldownTracker().setCooldown(this, 20); ++ } else { ++ // Paper end + if (entityhuman instanceof net.minecraft.server.level.EntityPlayer) { + ((net.minecraft.server.level.EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); + } +- return InteractionResultWrapper.fail(itemstack); ++ return new InteractionResultWrapper(net.minecraft.world.EnumInteractionResult.FAIL, itemstack); + } + } + +- world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_ENDER_PEARL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemEnderPearl.RANDOM.nextFloat() * 0.4F + 0.8F)); +- entityhuman.getCooldownTracker().setCooldown(this, 20); +- // CraftBukkit end +- +- entityhuman.b(StatisticList.ITEM_USED.b(this)); +- if (!entityhuman.abilities.canInstantlyBuild) { +- itemstack.subtract(1); +- } ++ // Paper start - moved up ++// world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_ENDER_PEARL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemEnderPearl.RANDOM.nextFloat() * 0.4F + 0.8F)); ++// entityhuman.getCooldownTracker().setCooldown(this, 20); ++// // CraftBukkit end ++// ++// entityhuman.b(StatisticList.ITEM_USED.b(this)); ++// if (!entityhuman.abilities.canInstantlyBuild) { ++// itemstack.subtract(1); ++// } ++ // Paper end - moved up + + return InteractionResultWrapper.a(itemstack, world.s_()); + } +diff --git a/src/main/java/net/minecraft/world/item/ItemExpBottle.java b/src/main/java/net/minecraft/world/item/ItemExpBottle.java +index 3f41fe5bf1a0cc283d6a72824779026fdad75708..cf36ec4769dc316e3ed16262043cb78cbba340ab 100644 +--- a/src/main/java/net/minecraft/world/item/ItemExpBottle.java ++++ b/src/main/java/net/minecraft/world/item/ItemExpBottle.java +@@ -1,10 +1,13 @@ + package net.minecraft.world.item; + ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.sounds.SoundCategory; + import net.minecraft.sounds.SoundEffects; + import net.minecraft.stats.StatisticList; + import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; + import net.minecraft.world.InteractionResultWrapper; ++import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.entity.projectile.EntityThrownExpBottle; + import net.minecraft.world.level.World; +@@ -24,19 +27,38 @@ public class ItemExpBottle extends Item { + public InteractionResultWrapper a(World world, EntityHuman entityhuman, EnumHand enumhand) { + ItemStack itemstack = entityhuman.b(enumhand); + +- world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_EXPERIENCE_BOTTLE_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemExpBottle.RANDOM.nextFloat() * 0.4F + 0.8F)); ++ //world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_EXPERIENCE_BOTTLE_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemExpBottle.RANDOM.nextFloat() * 0.4F + 0.8F)); // Paper - moved down + if (!world.isClientSide) { + EntityThrownExpBottle entitythrownexpbottle = new EntityThrownExpBottle(world, entityhuman); + + entitythrownexpbottle.setItem(itemstack); + entitythrownexpbottle.a(entityhuman, entityhuman.pitch, entityhuman.yaw, -20.0F, 0.7F, 1.0F); +- world.addEntity(entitythrownexpbottle); ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entitythrownexpbottle.getBukkitEntity()); ++ if (event.callEvent() && world.addEntity(entitythrownexpbottle)) { ++ if (event.shouldConsume() && !entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } else if (entityhuman instanceof EntityPlayer) { ++ ((EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); ++ } ++ ++ world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_EXPERIENCE_BOTTLE_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (Entity.SHARED_RANDOM.nextFloat() * 0.4F + 0.8F)); ++ entityhuman.b(StatisticList.ITEM_USED.b(this)); ++ } else { ++ if (entityhuman instanceof EntityPlayer) { ++ ((EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); ++ } ++ return new InteractionResultWrapper(EnumInteractionResult.FAIL, itemstack); ++ } ++ // Paper end + } + ++ /* // Paper start - moved up + entityhuman.b(StatisticList.ITEM_USED.b(this)); + if (!entityhuman.abilities.canInstantlyBuild) { + itemstack.subtract(1); + } ++ */ // Paper end + + return InteractionResultWrapper.a(itemstack, world.s_()); + } +diff --git a/src/main/java/net/minecraft/world/item/ItemFireworks.java b/src/main/java/net/minecraft/world/item/ItemFireworks.java +index 79e9be800385b94c4493bd8970620d76bfbd65ae..e7f958d137257da912ce9b83db017b4423959943 100644 +--- a/src/main/java/net/minecraft/world/item/ItemFireworks.java ++++ b/src/main/java/net/minecraft/world/item/ItemFireworks.java +@@ -3,6 +3,7 @@ package net.minecraft.world.item; + import java.util.Arrays; + import java.util.Comparator; + import net.minecraft.core.EnumDirection; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.world.EnumHand; + import net.minecraft.world.EnumInteractionResult; + import net.minecraft.world.InteractionResultWrapper; +@@ -29,8 +30,12 @@ public class ItemFireworks extends Item { + EntityFireworks entityfireworks = new EntityFireworks(world, itemactioncontext.getEntity(), vec3d.x + (double) enumdirection.getAdjacentX() * 0.15D, vec3d.y + (double) enumdirection.getAdjacentY() * 0.15D, vec3d.z + (double) enumdirection.getAdjacentZ() * 0.15D, itemstack); + entityfireworks.spawningEntity = itemactioncontext.getEntity().getUniqueID(); // Paper + +- world.addEntity(entityfireworks); +- itemstack.subtract(1); ++ // Paper start - PlayerLaunchProjectileEvent ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) itemactioncontext.getEntity().getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Firework) entityfireworks.getBukkitEntity()); ++ if (!event.callEvent() || !world.addEntity(entityfireworks)) return EnumInteractionResult.PASS; ++ if (event.shouldConsume() && !itemactioncontext.getEntity().abilities.canInstantlyBuild) itemstack.subtract(1); ++ else if (itemactioncontext.getEntity() instanceof EntityPlayer) ((EntityPlayer) itemactioncontext.getEntity()).getBukkitEntity().updateInventory(); ++ // Paper end + } + + return EnumInteractionResult.a(world.isClientSide); +diff --git a/src/main/java/net/minecraft/world/item/ItemLingeringPotion.java b/src/main/java/net/minecraft/world/item/ItemLingeringPotion.java +index a75f374f0639e8143772aa863666afe25d2020cf..0e073a8c23d24afb8b0198a9cfd8dc7d0b9d0a6b 100644 +--- a/src/main/java/net/minecraft/world/item/ItemLingeringPotion.java ++++ b/src/main/java/net/minecraft/world/item/ItemLingeringPotion.java +@@ -3,6 +3,7 @@ package net.minecraft.world.item; + import net.minecraft.sounds.SoundCategory; + import net.minecraft.sounds.SoundEffects; + import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; + import net.minecraft.world.InteractionResultWrapper; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.level.World; +@@ -15,7 +16,12 @@ public class ItemLingeringPotion extends ItemPotionThrowable { + + @Override + public InteractionResultWrapper a(World world, EntityHuman entityhuman, EnumHand enumhand) { +- world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_LINGERING_POTION_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemLingeringPotion.RANDOM.nextFloat() * 0.4F + 0.8F)); +- return super.a(world, entityhuman, enumhand); ++ // Paper start ++ InteractionResultWrapper wrapper = super.a(world, entityhuman, enumhand); ++ if (wrapper.getResult() != EnumInteractionResult.FAIL) { ++ world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_LINGERING_POTION_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemLingeringPotion.RANDOM.nextFloat() * 0.4F + 0.8F)); ++ } ++ return wrapper; ++ // Paper end + } + } +diff --git a/src/main/java/net/minecraft/world/item/ItemPotionThrowable.java b/src/main/java/net/minecraft/world/item/ItemPotionThrowable.java +index d050243946ad7023d5dd3958d7056cddcaf185a4..27c61fc4e61b0d76565ca6893514b3c73247c954 100644 +--- a/src/main/java/net/minecraft/world/item/ItemPotionThrowable.java ++++ b/src/main/java/net/minecraft/world/item/ItemPotionThrowable.java +@@ -1,7 +1,9 @@ + package net.minecraft.world.item; + ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.stats.StatisticList; + import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; + import net.minecraft.world.InteractionResultWrapper; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.entity.projectile.EntityPotion; +@@ -22,13 +24,31 @@ public class ItemPotionThrowable extends ItemPotion { + + entitypotion.setItem(itemstack); + entitypotion.a(entityhuman, entityhuman.pitch, entityhuman.yaw, -20.0F, 0.5F, 1.0F); +- world.addEntity(entitypotion); ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entitypotion.getBukkitEntity()); ++ if (event.callEvent() && world.addEntity(entitypotion)) { ++ if (event.shouldConsume() && !entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } else if (entityhuman instanceof EntityPlayer) { ++ ((EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); ++ } ++ ++ entityhuman.b(StatisticList.ITEM_USED.b(this)); ++ } else { ++ if (entityhuman instanceof EntityPlayer) { ++ ((EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); ++ } ++ return new InteractionResultWrapper(EnumInteractionResult.FAIL, itemstack); ++ } ++ // Paper end + } + ++ /* // Paper start - moved up + entityhuman.b(StatisticList.ITEM_USED.b(this)); + if (!entityhuman.abilities.canInstantlyBuild) { + itemstack.subtract(1); + } ++ */ // Paper end + + return InteractionResultWrapper.a(itemstack, world.s_()); + } +diff --git a/src/main/java/net/minecraft/world/item/ItemSnowball.java b/src/main/java/net/minecraft/world/item/ItemSnowball.java +index e5200b2a7d6d5c2d549e585ed157ec5217edae8e..8a1d59cb1ea5a8959c52272aa762ec35307246d7 100644 +--- a/src/main/java/net/minecraft/world/item/ItemSnowball.java ++++ b/src/main/java/net/minecraft/world/item/ItemSnowball.java +@@ -26,14 +26,20 @@ public class ItemSnowball extends Item { + + entitysnowball.setItem(itemstack); + entitysnowball.a(entityhuman, entityhuman.pitch, entityhuman.yaw, 0.0F, 1.5F, 1.0F); +- if (world.addEntity(entitysnowball)) { +- if (!entityhuman.abilities.canInstantlyBuild) { ++ // Paper start ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entitysnowball.getBukkitEntity()); ++ if (event.callEvent() && world.addEntity(entitysnowball)) { ++ if (event.shouldConsume() && !entityhuman.abilities.canInstantlyBuild) { ++ // Paper end + itemstack.subtract(1); ++ } else if (entityhuman instanceof net.minecraft.server.level.EntityPlayer) { // Paper ++ ((net.minecraft.server.level.EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); // Paper + } + + world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_SNOWBALL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemSnowball.RANDOM.nextFloat() * 0.4F + 0.8F)); +- } else if (entityhuman instanceof net.minecraft.server.level.EntityPlayer) { +- ((net.minecraft.server.level.EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); ++ } else { // Paper ++ if (entityhuman instanceof net.minecraft.server.level.EntityPlayer) ((net.minecraft.server.level.EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); // Paper ++ return new InteractionResultWrapper(net.minecraft.world.EnumInteractionResult.FAIL, itemstack); // Paper + } + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/item/ItemSplashPotion.java b/src/main/java/net/minecraft/world/item/ItemSplashPotion.java +index 98f29fac4bf087ad15f1cc7e85b408e22ec07efd..971491a461ccb7a707c6ca1a5b7c16d8823a7a80 100644 +--- a/src/main/java/net/minecraft/world/item/ItemSplashPotion.java ++++ b/src/main/java/net/minecraft/world/item/ItemSplashPotion.java +@@ -3,6 +3,7 @@ package net.minecraft.world.item; + import net.minecraft.sounds.SoundCategory; + import net.minecraft.sounds.SoundEffects; + import net.minecraft.world.EnumHand; ++import net.minecraft.world.EnumInteractionResult; + import net.minecraft.world.InteractionResultWrapper; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.level.World; +@@ -15,7 +16,12 @@ public class ItemSplashPotion extends ItemPotionThrowable { + + @Override + public InteractionResultWrapper a(World world, EntityHuman entityhuman, EnumHand enumhand) { ++ // Paper start ++ InteractionResultWrapper wrapper = super.a(world, entityhuman, enumhand); ++ if (wrapper.getResult() != EnumInteractionResult.FAIL) { + world.playSound((EntityHuman) null, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.ENTITY_SPLASH_POTION_THROW, SoundCategory.PLAYERS, 0.5F, 0.4F / (ItemSplashPotion.RANDOM.nextFloat() * 0.4F + 0.8F)); +- return super.a(world, entityhuman, enumhand); ++ } ++ return wrapper; ++ // Paper end + } + } diff --git a/patches/server-unmapped/0001/0388-Add-CraftMagicNumbers.isSupportedApiVersion.patch b/patches/server-unmapped/0001/0388-Add-CraftMagicNumbers.isSupportedApiVersion.patch new file mode 100644 index 0000000000..abf6eb1d92 --- /dev/null +++ b/patches/server-unmapped/0001/0388-Add-CraftMagicNumbers.isSupportedApiVersion.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BlackHole +Date: Sun, 15 Dec 2019 19:12:39 +0100 +Subject: [PATCH] Add CraftMagicNumbers.isSupportedApiVersion() + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 8aa9e7796ea39c09a965750d06c3d358250f33b8..7e4cceff7ce9ffaff00caf21088fd7bc59e66933 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -375,6 +375,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { + return new com.destroystokyo.paper.PaperVersionFetcher(); + } ++ ++ @Override ++ public boolean isSupportedApiVersion(String apiVersion) { ++ return apiVersion != null && SUPPORTED_API.contains(apiVersion); ++ } + // Paper end + + /** diff --git a/patches/server-unmapped/0001/0389-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch b/patches/server-unmapped/0001/0389-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch new file mode 100644 index 0000000000..0188a5ae7f --- /dev/null +++ b/patches/server-unmapped/0001/0389-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Callahan +Date: Mon, 13 Jan 2020 23:47:28 -0600 +Subject: [PATCH] Prevent sync chunk loads when villagers try to find beds + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorSleep.java b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorSleep.java +index 84eba4c91e8e608b84623d6c71233e2512b77a54..ce86301723d20e79f95207cce1084bac091742fe 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorSleep.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorSleep.java +@@ -46,7 +46,8 @@ public class BehaviorSleep extends Behavior { + } + } + +- IBlockData iblockdata = worldserver.getType(globalpos.getBlockPosition()); ++ IBlockData iblockdata = worldserver.getTypeIfLoaded(globalpos.getBlockPosition()); // Paper ++ if (iblockdata == null) { return false; } // Paper + + return globalpos.getBlockPosition().a((IPosition) entityliving.getPositionVector(), 2.0D) && iblockdata.getBlock().a((Tag) TagsBlock.BEDS) && !(Boolean) iblockdata.get(BlockBed.OCCUPIED); + } diff --git a/patches/server-unmapped/0001/0390-MC-145656-Fix-Follow-Range-Initial-Target.patch b/patches/server-unmapped/0001/0390-MC-145656-Fix-Follow-Range-Initial-Target.patch new file mode 100644 index 0000000000..342a7deea1 --- /dev/null +++ b/patches/server-unmapped/0001/0390-MC-145656-Fix-Follow-Range-Initial-Target.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 18 Dec 2019 22:21:35 -0600 +Subject: [PATCH] MC-145656 Fix Follow Range Initial Target + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index e726b6213cf2e8f5b326f05c0438b8f1ee2b73c5..edda2121f8c1046478beaa77030ebb36d403b334 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -580,4 +580,9 @@ public class PaperWorldConfig { + private void pillagerSettings() { + disablePillagerPatrols = getBoolean("game-mechanics.disable-pillager-patrols", disablePillagerPatrols); + } ++ ++ public boolean entitiesTargetWithFollowRange = false; ++ private void entitiesTargetWithFollowRange() { ++ entitiesTargetWithFollowRange = getBoolean("entities-target-with-follow-range", entitiesTargetWithFollowRange); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/target/PathfinderGoalNearestAttackableTarget.java b/src/main/java/net/minecraft/world/entity/ai/goal/target/PathfinderGoalNearestAttackableTarget.java +index 4f0a2cbdd6d42e3e4721345e21bf0ef33ec48e1e..44f21c3f7af17e9d39777a48c6715a22fc085da6 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/target/PathfinderGoalNearestAttackableTarget.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/target/PathfinderGoalNearestAttackableTarget.java +@@ -32,6 +32,7 @@ public class PathfinderGoalNearestAttackableTarget exten + this.b = i; + this.a(EnumSet.of(PathfinderGoal.Type.TARGET)); + this.d = (new PathfinderTargetCondition()).a(this.k()).a(predicate); ++ if (entityinsentient.world.paperConfig.entitiesTargetWithFollowRange) this.d.useFollowRange(); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/PathfinderTargetCondition.java b/src/main/java/net/minecraft/world/entity/ai/targeting/PathfinderTargetCondition.java +index 0de32bcf24a94efe5af922b877d4cdc3578e0cbd..e6988f7ea428f1503e3db63876b13e57f898ee30 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/targeting/PathfinderTargetCondition.java ++++ b/src/main/java/net/minecraft/world/entity/ai/targeting/PathfinderTargetCondition.java +@@ -4,6 +4,8 @@ import java.util.function.Predicate; + import javax.annotation.Nullable; + import net.minecraft.world.entity.EntityInsentient; + import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.ai.attributes.AttributeModifiable; ++import net.minecraft.world.entity.ai.attributes.GenericAttributes; + + public class PathfinderTargetCondition { + +@@ -82,7 +84,7 @@ public class PathfinderTargetCondition { + + if (this.b > 0.0D) { + double d0 = this.g ? entityliving1.A(entityliving) : 1.0D; +- double d1 = Math.max(this.b * d0, 2.0D); ++ double d1 = Math.max((useFollowRange ? getFollowRange(entityliving) : this.b) * d0, 2.0D); // Paper + double d2 = entityliving.h(entityliving1.locX(), entityliving1.locY(), entityliving1.locZ()); + + if (d2 > d1 * d1) { +@@ -98,4 +100,18 @@ public class PathfinderTargetCondition { + return true; + } + } ++ ++ // Paper start ++ private boolean useFollowRange = false; ++ ++ public PathfinderTargetCondition useFollowRange() { ++ this.useFollowRange = true; ++ return this; ++ } ++ ++ private double getFollowRange(EntityLiving entityliving) { ++ AttributeModifiable attributeinstance = entityliving.getAttributeInstance(GenericAttributes.FOLLOW_RANGE); ++ return attributeinstance == null ? 16.0D : attributeinstance.getValue(); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0391-Optimize-Hoppers.patch b/patches/server-unmapped/0001/0391-Optimize-Hoppers.patch new file mode 100644 index 0000000000..375ecefc7c --- /dev/null +++ b/patches/server-unmapped/0001/0391-Optimize-Hoppers.patch @@ -0,0 +1,517 @@ +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 +* 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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index edda2121f8c1046478beaa77030ebb36d403b334..7fbd501d70dccf869a4454e2789a5d68f2e15754 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -585,4 +585,13 @@ public class PaperWorldConfig { + private void entitiesTargetWithFollowRange() { + entitiesTargetWithFollowRange = getBoolean("entities-target-with-follow-range", entitiesTargetWithFollowRange); + } ++ ++ public boolean cooldownHopperWhenFull = true; ++ public boolean disableHopperMoveEvents = false; ++ private void hopperOptimizations() { ++ cooldownHopperWhenFull = getBoolean("hopper.cooldown-when-full", cooldownHopperWhenFull); ++ log("Cooldown Hoppers when Full: " + (cooldownHopperWhenFull ? "enabled" : "disabled")); ++ disableHopperMoveEvents = getBoolean("hopper.disable-move-event", disableHopperMoveEvents); ++ log("Hopper Move Item Events: " + (disableHopperMoveEvents ? "disabled" : "enabled")); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 0a407b7d2c87e2fc745eedf7b3ea794ab0211716..af7bef41d341218f25943eb1e10831cc0be6840a 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -128,6 +128,7 @@ import net.minecraft.world.level.WorldSettings; + import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.biome.WorldChunkManager; + import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.entity.TileEntityHopper; + import net.minecraft.world.level.border.IWorldBorderListener; + import net.minecraft.world.level.border.WorldBorder; + import net.minecraft.world.level.chunk.ChunkGenerator; +@@ -1361,6 +1362,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0; // Paper ++ TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + + this.methodProfiler.a(() -> { + return worldserver + " " + worldserver.getDimensionKey().a(); +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 30db766c54db08a472caef82fdcc7cf1b7855fbf..661f400ae4f5cebef5d1743819529ecf647b6681 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -540,11 +540,12 @@ public final class ItemStack { + return this.getItem().a(this, entityhuman, entityliving, enumhand); + } + +- public ItemStack cloneItemStack() { +- if (this.isEmpty()) { ++ public ItemStack cloneItemStack() { return cloneItemStack(false); } // Paper ++ public ItemStack cloneItemStack(boolean origItem) { // Paper ++ if (!origItem && this.isEmpty()) { // Paper + return ItemStack.b; + } else { +- ItemStack itemstack = new ItemStack(this.getItem(), this.count); ++ ItemStack itemstack = new ItemStack(origItem ? this.item : this.getItem(), this.count); // Paper + + itemstack.d(this.D()); + if (this.tag != null) { +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 6781b25cc8e15be2556bb1bb8dc8c18c106b40ec..d1738b57efd3f5e6c51603553a773173e4b09bb5 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -1162,8 +1162,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return list; + } + +- @Override +- public List a(Class oclass, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { ++ public List getEntities(Class oclass, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { return a(oclass, axisalignedbb, predicate); } // Paper - OBFHELPER ++ @Override public List a(Class oclass, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { + this.getMethodProfiler().c("getEntities"); + int i = MathHelper.floor((axisalignedbb.minX - 2.0D) / 16.0D); + int j = MathHelper.f((axisalignedbb.maxX + 2.0D) / 16.0D); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/IHopper.java b/src/main/java/net/minecraft/world/level/block/entity/IHopper.java +index d0943ae1f372784716195666212ff83e6ee4873e..1db7b7bfe98658d0b20800a4178556f8daaf881a 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/IHopper.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/IHopper.java +@@ -1,6 +1,7 @@ + package net.minecraft.world.level.block.entity; + + import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; + import net.minecraft.world.IInventory; + import net.minecraft.world.level.World; + import net.minecraft.world.level.block.Block; +@@ -17,12 +18,13 @@ public interface IHopper extends IInventory { + return IHopper.c; + } + +- @Nullable ++ //@Nullable // Paper - it's annoying + World getWorld(); ++ default BlockPosition getBlockPosition() { return new BlockPosition(getX(), getY(), getZ()); } // Paper + +- double x(); ++ double x(); default double getX() { return this.x(); } // Paper - OBFHELPER + +- double z(); ++ double z(); default double getY() { return this.z(); } // Paper - OBFHELPER + +- double A(); ++ double A(); default double getZ() { return this.A(); } // Paper - OBFHELPER + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +index 48daa039ffa8ccb7b6f3ca47bdc56394addf9254..f1e586754396439dfb70a4d63e3b8b34fb36ebf4 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +@@ -77,6 +77,7 @@ public abstract class TileEntity implements net.minecraft.server.KeyedObject { / + public void setCurrentChunk(Chunk chunk) { + this.currentChunk = chunk != null ? new java.lang.ref.WeakReference<>(chunk) : null; + } ++ static boolean IGNORE_TILE_UPDATES = false; + // Paper end + + @Nullable +@@ -155,6 +156,7 @@ public abstract class TileEntity implements net.minecraft.server.KeyedObject { / + + public void update() { + if (this.world != null) { ++ if (IGNORE_TILE_UPDATES) return; // Paper + this.c = this.world.getType(this.position); + this.world.b(this.position, this); + if (!this.c.isAir()) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityHopper.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityHopper.java +index 5fe715e8dbe9d925170acce6e0f18312d9f998f2..537dc52e5ff3325555ee6049bc7f277952983b76 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityHopper.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityHopper.java +@@ -196,6 +196,160 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + return false; + } + ++ // Paper start - Optimize Hoppers ++ private static boolean skipPullModeEventFire = false; ++ private static boolean skipPushModeEventFire = false; ++ public static boolean skipHopperEvents = false; ++ ++ private boolean hopperPush(IInventory iinventory, EnumDirection enumdirection) { ++ skipPushModeEventFire = skipHopperEvents; ++ boolean foundItem = false; ++ for (int i = 0; i < this.getSize(); ++i) { ++ ItemStack item = this.getItem(i); ++ if (!item.isEmpty()) { ++ foundItem = true; ++ ItemStack origItemStack = item; ++ ItemStack itemstack = origItemStack; ++ ++ final int origCount = origItemStack.getCount(); ++ final int moved = Math.min(world.spigotConfig.hopperAmount, origCount); ++ origItemStack.setCount(moved); ++ ++ // 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) { ++ itemstack = callPushMoveEvent(iinventory, itemstack); ++ if (itemstack == null) { // cancelled ++ origItemStack.setCount(origCount); ++ return false; ++ } ++ } ++ final ItemStack itemstack2 = addItem(this, iinventory, itemstack, enumdirection); ++ final int remaining = itemstack2.getCount(); ++ if (remaining != moved) { ++ origItemStack = origItemStack.cloneItemStack(true); ++ origItemStack.setCount(origCount); ++ if (!origItemStack.isEmpty()) { ++ origItemStack.setCount(origCount - moved + remaining); ++ } ++ this.setItem(i, origItemStack); ++ iinventory.update(); ++ return true; ++ } ++ origItemStack.setCount(origCount); ++ } ++ } ++ if (foundItem && world.paperConfig.cooldownHopperWhenFull) { // Inventory was full - cooldown ++ this.setCooldown(world.spigotConfig.hopperTransfer); ++ } ++ return false; ++ } ++ ++ private static boolean hopperPull(IHopper ihopper, IInventory iinventory, ItemStack origItemStack, int i) { ++ ItemStack itemstack = origItemStack; ++ final int origCount = origItemStack.getCount(); ++ final World world = ihopper.getWorld(); ++ final int moved = Math.min(world.spigotConfig.hopperAmount, origCount); ++ itemstack.setCount(moved); ++ ++ if (!skipPullModeEventFire) { ++ itemstack = callPullMoveEvent(ihopper, iinventory, itemstack); ++ if (itemstack == null) { // cancelled ++ origItemStack.setCount(origCount); ++ // 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 itemstack2 = addItem(iinventory, ihopper, itemstack, null); ++ final int remaining = itemstack2.getCount(); ++ if (remaining != moved) { ++ origItemStack = origItemStack.cloneItemStack(true); ++ origItemStack.setCount(origCount); ++ if (!origItemStack.isEmpty()) { ++ origItemStack.setCount(origCount - moved + remaining); ++ } ++ IGNORE_TILE_UPDATES = true; ++ iinventory.setItem(i, origItemStack); ++ IGNORE_TILE_UPDATES = false; ++ iinventory.update(); ++ return true; ++ } ++ origItemStack.setCount(origCount); ++ ++ if (world.paperConfig.cooldownHopperWhenFull) { ++ cooldownHopper(ihopper); ++ } ++ ++ return false; ++ } ++ ++ private ItemStack callPushMoveEvent(IInventory iinventory, ItemStack itemstack) { ++ Inventory destinationInventory = getInventory(iinventory); ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(this.getOwner(false).getInventory(), ++ CraftItemStack.asCraftMirror(itemstack), destinationInventory, true); ++ boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPushModeEventFire = true; ++ } ++ if (!result) { ++ cooldownHopper(this); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemstack; ++ } ++ } ++ ++ private static ItemStack callPullMoveEvent(IHopper hopper, IInventory iinventory, ItemStack itemstack) { ++ Inventory sourceInventory = getInventory(iinventory); ++ Inventory destination = getInventory(hopper); ++ ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, ++ // Mirror is safe as we no plugins ever use this item ++ CraftItemStack.asCraftMirror(itemstack), destination, false); ++ 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(IInventory iinventory) { ++ Inventory sourceInventory;// Have to special case large chests as they work oddly ++ if (iinventory instanceof InventoryLargeChest) { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory); ++ } else if (iinventory instanceof TileEntity) { ++ sourceInventory = ((TileEntity) iinventory).getOwner(false).getInventory(); ++ } else { ++ sourceInventory = iinventory.getOwner().getInventory(); ++ } ++ return sourceInventory; ++ } ++ ++ private static void cooldownHopper(IHopper hopper) { ++ if (hopper instanceof TileEntityHopper) { ++ ((TileEntityHopper) hopper).setCooldown(hopper.getWorld().spigotConfig.hopperTransfer); ++ } else if (hopper instanceof EntityMinecartHopper) { ++ ((EntityMinecartHopper) hopper).setCooldown(hopper.getWorld().spigotConfig.hopperTransfer / 2); ++ } ++ } ++ // Paper end ++ + private boolean k() { + IInventory iinventory = this.l(); + +@@ -207,6 +361,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + if (this.b(iinventory, enumdirection)) { + return false; + } else { ++ return hopperPush(iinventory, enumdirection); /* // Paper - disable rest + for (int i = 0; i < this.getSize(); ++i) { + if (!this.getItem(i).isEmpty()) { + ItemStack itemstack = this.getItem(i).cloneItemStack(); +@@ -244,7 +399,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + } + } + +- return false; ++ return false;*/ // Paper - end commenting out replaced block for Hopper Optimizations + } + } + } +@@ -253,18 +408,54 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + return iinventory instanceof IWorldInventory ? IntStream.of(((IWorldInventory) iinventory).getSlotsForFace(enumdirection)) : IntStream.range(0, iinventory.getSize()); + } + +- private boolean b(IInventory iinventory, EnumDirection enumdirection) { +- return a(iinventory, enumdirection).allMatch((i) -> { +- ItemStack itemstack = iinventory.getItem(i); ++ private static boolean allMatch(IInventory iinventory, EnumDirection enumdirection, java.util.function.BiPredicate test) { ++ if (iinventory instanceof IWorldInventory) { ++ for (int i : ((IWorldInventory) iinventory).getSlotsForFace(enumdirection)) { ++ if (!test.test(iinventory.getItem(i), i)) { ++ return false; ++ } ++ } ++ } else { ++ int size = iinventory.getSize(); ++ for (int i = 0; i < size; i++) { ++ if (!test.test(iinventory.getItem(i), i)) { ++ return false; ++ } ++ } ++ } ++ return true; ++ } + +- return itemstack.getCount() >= itemstack.getMaxStackSize(); +- }); ++ private static boolean anyMatch(IInventory iinventory, EnumDirection enumdirection, java.util.function.BiPredicate test) { ++ if (iinventory instanceof IWorldInventory) { ++ for (int i : ((IWorldInventory) iinventory).getSlotsForFace(enumdirection)) { ++ if (test.test(iinventory.getItem(i), i)) { ++ return true; ++ } ++ } ++ } else { ++ int size = iinventory.getSize(); ++ 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 ++ ++ private boolean b(IInventory iinventory, EnumDirection enumdirection) { ++ // Paper start - no streams ++ return allMatch(iinventory, enumdirection, STACK_SIZE_TEST); ++ // Paper end + } + + private static boolean c(IInventory iinventory, EnumDirection enumdirection) { +- return a(iinventory, enumdirection).allMatch((i) -> { +- return iinventory.getItem(i).isEmpty(); +- }); ++ return allMatch(iinventory, enumdirection, IS_EMPTY_TEST); + } + + public static boolean a(IHopper ihopper) { +@@ -273,9 +464,17 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + if (iinventory != null) { + EnumDirection enumdirection = EnumDirection.DOWN; + +- return c(iinventory, enumdirection) ? false : a(iinventory, enumdirection).anyMatch((i) -> { +- return a(ihopper, iinventory, i, enumdirection); ++ // Paper start - optimize hoppers and remove streams ++ skipPullModeEventFire = skipHopperEvents; ++ return !c(iinventory, enumdirection) && anyMatch(iinventory, enumdirection, (item, i) -> { ++ // Logic copied from below to avoid extra getItem calls ++ if (!item.isEmpty() && canTakeItem(iinventory, item, i, enumdirection)) { ++ return hopperPull(ihopper, iinventory, item, i); ++ } else { ++ return false; ++ } + }); ++ // Paper end + } else { + Iterator iterator = c(ihopper).iterator(); + +@@ -293,10 +492,11 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + } + } + +- private static boolean a(IHopper ihopper, IInventory iinventory, int i, EnumDirection enumdirection) { ++ private static boolean a(IHopper ihopper, IInventory iinventory, int i, EnumDirection enumdirection) {// Paper - method unused as logic is inlined above + ItemStack itemstack = iinventory.getItem(i); + +- if (!itemstack.isEmpty() && b(iinventory, itemstack, i, enumdirection)) { ++ if (!itemstack.isEmpty() && b(iinventory, itemstack, i, enumdirection)) { // If this logic changes, update above. this is left inused incase reflective plugins ++ return hopperPull(ihopper, iinventory, itemstack, i); /* // Paper - disable rest + ItemStack itemstack1 = itemstack.cloneItemStack(); + // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.splitStack(i, 1), (EnumDirection) null); + // CraftBukkit start - Call event on collection of items from inventories into the hopper +@@ -333,7 +533,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + } + + itemstack1.subtract(origCount - itemstack2.getCount()); // Spigot +- iinventory.setItem(i, itemstack1); ++ iinventory.setItem(i, itemstack1);*/ // Paper - end commenting out replaced block for Hopper Optimizations + } + + return false; +@@ -342,7 +542,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + public static boolean a(IInventory iinventory, EntityItem entityitem) { + boolean flag = false; + // CraftBukkit start +- InventoryPickupItemEvent event = new InventoryPickupItemEvent(iinventory.getOwner().getInventory(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); ++ InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(iinventory), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); // Paper - use getInventory() to avoid snapshot creation + entityitem.world.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; +@@ -384,6 +584,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + return !iinventory.b(i, itemstack) ? false : !(iinventory instanceof IWorldInventory) || ((IWorldInventory) iinventory).canPlaceItemThroughFace(i, itemstack, enumdirection); + } + ++ private static boolean canTakeItem(IInventory iinventory, ItemStack itemstack, int i, EnumDirection enumdirection) { return b(iinventory, itemstack, i, enumdirection); } // Paper - OBFHELPER + private static boolean b(IInventory iinventory, ItemStack itemstack, int i, EnumDirection enumdirection) { + return !(iinventory instanceof IWorldInventory) || ((IWorldInventory) iinventory).canTakeItemThroughFace(i, itemstack, enumdirection); + } +@@ -396,7 +597,9 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + boolean flag1 = iinventory1.isEmpty(); + + if (itemstack1.isEmpty()) { ++ IGNORE_TILE_UPDATES = true; // Paper + iinventory1.setItem(i, itemstack); ++ IGNORE_TILE_UPDATES = false; // Paper + itemstack = ItemStack.b; + flag = true; + } else if (a(itemstack1, itemstack)) { +@@ -447,18 +650,24 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + } + + public static List c(IHopper ihopper) { +- return (List) ihopper.aa_().d().stream().flatMap((axisalignedbb) -> { +- return ihopper.getWorld().a(EntityItem.class, axisalignedbb.d(ihopper.x() - 0.5D, ihopper.z() - 0.5D, ihopper.A() - 0.5D), IEntitySelector.a).stream(); +- }).collect(Collectors.toList()); ++ // Paper start - Optimize item suck in. remove streams, restore 1.12 checks. Seriously checking the bowl?! ++ World world = ihopper.getWorld(); ++ double d0 = ihopper.getX(); ++ double d1 = ihopper.getY(); ++ double d2 = ihopper.getZ(); ++ AxisAlignedBB bb = new AxisAlignedBB(d0 - 0.5D, d1, d2 - 0.5D, d0 + 0.5D, d1 + 1.5D, d2 + 0.5D); ++ return world.getEntities(EntityItem.class, bb, Entity::isAlive); ++ // Paper end + } + + @Nullable + public static IInventory b(World world, BlockPosition blockposition) { +- return a(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D); ++ return a(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, true); // Paper + } + + @Nullable +- public static IInventory a(World world, double d0, double d1, double d2) { ++ public static IInventory a(World world, double d0, double d1, double d2) { return a(world, d0, d1, d2, false); } // Paper - overload to default false ++ public static IInventory a(World world, double d0, double d1, double d2, boolean optimizeEntities) { // Paper + Object object = null; + BlockPosition blockposition = new BlockPosition(d0, d1, d2); + if ( !world.isLoaded( blockposition ) ) return null; // Spigot +@@ -478,7 +687,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + } + } + +- if (object == null) { ++ if (object == null && (!optimizeEntities || !org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(block).isOccluding())) { // Paper + List list = world.getEntities((Entity) null, new AxisAlignedBB(d0 - 0.5D, d1 - 0.5D, d2 - 0.5D, d0 + 0.5D, d1 + 0.5D, d2 + 0.5D), IEntitySelector.d); + + if (!list.isEmpty()) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityLootable.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityLootable.java +index 1508e267a38555820e2d31f3075adca185fbd4b6..f0da819627035bed83561128a11059424d2b7e30 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityLootable.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityLootable.java +@@ -97,12 +97,19 @@ public abstract class TileEntityLootable extends TileEntityContainer { + @Override + public boolean isEmpty() { + this.d((EntityHuman) null); +- return this.f().stream().allMatch(ItemStack::isEmpty); ++ // Paper start ++ for (ItemStack itemStack : this.f()) { ++ if (!itemStack.isEmpty()) { ++ return false; ++ } ++ } ++ // Paper end ++ return true; + } + + @Override + public ItemStack getItem(int i) { +- this.d((EntityHuman) null); ++ if (i == 0) this.d((EntityHuman) null); // Paper + return (ItemStack) this.f().get(i); + } + diff --git a/patches/server-unmapped/0001/0392-PlayerDeathEvent-shouldDropExperience.patch b/patches/server-unmapped/0001/0392-PlayerDeathEvent-shouldDropExperience.patch new file mode 100644 index 0000000000..fa7dbe8cb6 --- /dev/null +++ b/patches/server-unmapped/0001/0392-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/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 23bb5cd0b10c211019ff0b71128bbf835238e9d8..f242330bcf3d63490b5e7be36f8af6eccfb07820 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -821,7 +821,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.eW(); + } + // 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 - replace logic diff --git a/patches/server-unmapped/0001/0393-Prevent-bees-loading-chunks-checking-hive-position.patch b/patches/server-unmapped/0001/0393-Prevent-bees-loading-chunks-checking-hive-position.patch new file mode 100644 index 0000000000..224000580d --- /dev/null +++ b/patches/server-unmapped/0001/0393-Prevent-bees-loading-chunks-checking-hive-position.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 5 Jan 2020 17:24:34 -0600 +Subject: [PATCH] Prevent bees loading chunks checking hive position + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityBee.java b/src/main/java/net/minecraft/world/entity/animal/EntityBee.java +index 1554f1d1d35084c283b573b3e58e48c6747fe7d6..1ecf73f874f404f58a99316ae027f76db6b557db 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityBee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityBee.java +@@ -442,6 +442,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + if (!this.hasHivePos()) { + return false; + } else { ++ if (world.getChunkIfLoadedImmediately(hivePos.getX() >> 4, hivePos.getZ() >> 4) == null) return true; // Paper - just assume the hive is still there, no need to load the chunk(s) + TileEntity tileentity = this.world.getTileEntity(this.hivePos); + + return tileentity != null && tileentity.getTileType() == TileEntityTypes.BEEHIVE; diff --git a/patches/server-unmapped/0001/0394-Don-t-load-Chunks-from-Hoppers-and-other-things.patch b/patches/server-unmapped/0001/0394-Don-t-load-Chunks-from-Hoppers-and-other-things.patch new file mode 100644 index 0000000000..34cdbbb716 --- /dev/null +++ b/patches/server-unmapped/0001/0394-Don-t-load-Chunks-from-Hoppers-and-other-things.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Nov 2016 20:28:12 -0400 +Subject: [PATCH] Don't load Chunks from Hoppers and other things + +Hoppers call this to I guess "get the primary side" of a double sided chest. + +If the double sided chest crosses chunk lines, it causes the chunk to load. +This will end up causing sync chunk loads, which will unload with Chunk GC, +only to be reloaded again the next tick. + +This of course is undesirable, so just return the loaded side as "primary" +and treat it as a single chest if the other sides are unloaded + +diff --git a/src/main/java/net/minecraft/world/level/block/DoubleBlockFinder.java b/src/main/java/net/minecraft/world/level/block/DoubleBlockFinder.java +index d51f89fed6129c4b37ef63971f8f61dc14e8032d..efa98e87eead036246348f3915b401f0f52f2242 100644 +--- a/src/main/java/net/minecraft/world/level/block/DoubleBlockFinder.java ++++ b/src/main/java/net/minecraft/world/level/block/DoubleBlockFinder.java +@@ -29,7 +29,12 @@ public class DoubleBlockFinder { + return new DoubleBlockFinder.Result.Single<>(s0); + } else { + BlockPosition blockposition1 = blockposition.shift((EnumDirection) function1.apply(iblockdata)); +- IBlockData iblockdata1 = generatoraccess.getType(blockposition1); ++ // Paper start ++ IBlockData iblockdata1 = generatoraccess.getTypeIfLoaded(blockposition1); ++ if (iblockdata1 == null) { ++ return new DoubleBlockFinder.Result.Single<>(s0); ++ } ++ // Paper end + + if (iblockdata1.a(iblockdata.getBlock())) { + DoubleBlockFinder.BlockType doubleblockfinder_blocktype1 = (DoubleBlockFinder.BlockType) function.apply(iblockdata1); diff --git a/patches/server-unmapped/0001/0395-Guard-against-serializing-mismatching-chunk-coordina.patch b/patches/server-unmapped/0001/0395-Guard-against-serializing-mismatching-chunk-coordina.patch new file mode 100644 index 0000000000..1c55da068c --- /dev/null +++ b/patches/server-unmapped/0001/0395-Guard-against-serializing-mismatching-chunk-coordina.patch @@ -0,0 +1,62 @@ +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/ChunkRegionLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +index c95fcdf47db8bfe59a83c0d28f4744b4d8540ef8..e16e046d165330326ed220c9c440a637007f3137 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +@@ -67,6 +67,13 @@ public class ChunkRegionLoader { + + private static final Logger LOGGER = LogManager.getLogger(); + ++ // Paper start - guard against serializing mismatching coordinates ++ // TODO Note: This needs to be re-checked each update ++ public static ChunkCoordIntPair getChunkCoordinate(NBTTagCompound chunkData) { ++ NBTTagCompound levelData = chunkData.getCompound("Level"); ++ return new ChunkCoordIntPair(levelData.getInt("xPos"), levelData.getInt("zPos")); ++ } ++ // Paper end + // Paper start + public static final class InProgressChunkHolder { + +@@ -92,8 +99,8 @@ public class ChunkRegionLoader { + // Paper end + ChunkGenerator chunkgenerator = worldserver.getChunkProvider().getChunkGenerator(); + WorldChunkManager worldchunkmanager = chunkgenerator.getWorldChunkManager(); +- NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level"); +- ChunkCoordIntPair chunkcoordintpair1 = new ChunkCoordIntPair(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); ++ NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level"); // Paper - diff on change, see ChunkRegionLoader#getChunkCoordinate ++ ChunkCoordIntPair chunkcoordintpair1 = new ChunkCoordIntPair(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); // Paper - diff on change, see ChunkRegionLoader#getChunkCoordinate + + if (!Objects.equals(chunkcoordintpair, chunkcoordintpair1)) { + ChunkRegionLoader.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", chunkcoordintpair, chunkcoordintpair, chunkcoordintpair1); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java +index 01ae13385dd0208c9f34da8b3897b571f86305d0..890362d28ab9cb760c73fe5014e144fb08ada6b8 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/IChunkLoader.java +@@ -13,6 +13,7 @@ import net.minecraft.SharedConstants; + import net.minecraft.nbt.GameProfileSerializer; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.resources.ResourceKey; ++import net.minecraft.server.level.PlayerChunkMap; + import net.minecraft.util.datafix.DataFixTypes; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.World; +@@ -123,6 +124,13 @@ public class IChunkLoader implements AutoCloseable { + + public void a(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws IOException { write(chunkcoordintpair, nbttagcompound); } // Paper OBFHELPER + public void write(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws IOException { // Paper - OBFHELPER - (Switched around for safety) ++ // Paper start ++ if (!chunkcoordintpair.equals(ChunkRegionLoader.getChunkCoordinate(nbttagcompound))) { ++ String world = (this instanceof PlayerChunkMap) ? ((PlayerChunkMap)this).world.getWorld().getName() : null; ++ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkcoordintpair.toString() ++ + " but compound says coordinate is " + ChunkRegionLoader.getChunkCoordinate(nbttagcompound).toString() + (world == null ? " for an unknown world" : (" for world: " + world))); ++ } ++ // Paper end + this.regionFileCache.write(chunkcoordintpair, nbttagcompound); + if (this.c != null) { + synchronized (this.persistentDataLock) { // Paper - Async chunk loading diff --git a/patches/server-unmapped/0001/0396-Optimise-IEntityAccess-getPlayerByUUID.patch b/patches/server-unmapped/0001/0396-Optimise-IEntityAccess-getPlayerByUUID.patch new file mode 100644 index 0000000000..e33401ace0 --- /dev/null +++ b/patches/server-unmapped/0001/0396-Optimise-IEntityAccess-getPlayerByUUID.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 11 Jan 2020 21:50:56 -0800 +Subject: [PATCH] Optimise IEntityAccess#getPlayerByUUID + +Use the world entity map instead of iterating over all players + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 71967bc86c2c2ad6bcafc80a5f8ab31c33f022e6..07b2f7f13044ace4db48274da8309c37aaa2d2c4 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -289,6 +289,15 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + // Paper end + ++ // Paper start - optimise getPlayerByUUID ++ @Nullable ++ @Override ++ public EntityHuman getPlayerByUUID(UUID uuid) { ++ Entity player = this.entitiesByUUID.get(uuid); ++ return (player instanceof EntityHuman) ? (EntityHuman)player : null; ++ } ++ // Paper end ++ + // Add env and gen to constructor, WorldData -> WorldDataServer + public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { + super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor +diff --git a/src/main/java/net/minecraft/world/level/IEntityAccess.java b/src/main/java/net/minecraft/world/level/IEntityAccess.java +index 8fdc4b22e8c99d653bd213fe64339c133b46b4e9..1ff9e771788a4ab52129070e355ca48df2949470 100644 +--- a/src/main/java/net/minecraft/world/level/IEntityAccess.java ++++ b/src/main/java/net/minecraft/world/level/IEntityAccess.java +@@ -277,6 +277,12 @@ public interface IEntityAccess { + + @Nullable + default EntityHuman b(UUID uuid) { ++ // Paper start - allow WorldServer to override ++ return this.getPlayerByUUID(uuid); ++ } ++ @Nullable ++ default EntityHuman getPlayerByUUID(UUID uuid) { ++ // Paper end + for (int i = 0; i < this.getPlayers().size(); ++i) { + EntityHuman entityhuman = (EntityHuman) this.getPlayers().get(i); + diff --git a/patches/server-unmapped/0001/0397-Fix-items-not-falling-correctly.patch b/patches/server-unmapped/0001/0397-Fix-items-not-falling-correctly.patch new file mode 100644 index 0000000000..f9047f957b --- /dev/null +++ b/patches/server-unmapped/0001/0397-Fix-items-not-falling-correctly.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AJMFactsheets +Date: Fri, 17 Jan 2020 17:17:54 -0600 +Subject: [PATCH] Fix items not falling correctly + +Since 1.14, Mojang has added an optimization which skips checking if +an item should fall every fourth tick. + +However, Spigot's entity activation range class also has an +optimization which skips ticking active entities every fourth tick. +This can result in a state where an item will never properly fall +due to its move method never being called. + +This patch resolves the conflict by offsetting checking an item's +move method from Spigot's entity activation range check. + +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityItem.java b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +index 0b2e6e72a85e05f239d56afb6785c91da5b25d55..11e029f6f97f1dd9c32e311d1a3800f2fa54b91f 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityItem.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +@@ -116,7 +116,7 @@ public class EntityItem extends Entity { + } + } + +- if (!this.onGround || c(this.getMot()) > 9.999999747378752E-6D || (this.ticksLived + this.getId()) % 4 == 0) { ++ if (!this.onGround || c(this.getMot()) > 9.999999747378752E-6D || this.ticksLived % 4 == 0) { // Paper - Ensure checking item movement is always offset from Spigot's entity activation range check + this.move(EnumMoveType.SELF, this.getMot()); + float f1 = 0.98F; + diff --git a/patches/server-unmapped/0001/0398-Lag-compensate-eating.patch b/patches/server-unmapped/0001/0398-Lag-compensate-eating.patch new file mode 100644 index 0000000000..674e071d42 --- /dev/null +++ b/patches/server-unmapped/0001/0398-Lag-compensate-eating.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 14 Jan 2020 15:28:28 -0800 +Subject: [PATCH] Lag compensate eating + +When the server is lagging, players will wait longer when eating. +Change to also use a time check instead if it passes. + +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index ec31585099c8376f61496f02f9454cb600104918..f851a9806e3b936093275cf404caca82c6662ab4 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -214,7 +214,7 @@ public abstract class EntityLiving extends Entity { + private int jumpTicks; + private float bw; + public ItemStack activeItem; // Paper - public +- protected int bd; ++ protected int bd; protected final int getEatTimeTicks() { return this.bd; } protected final void setEatTimeTicks(int value) { this.bd = value; } // Paper - OBFHELPER + protected int be; + private BlockPosition bx; + private Optional by; +@@ -3151,6 +3151,11 @@ public abstract class EntityLiving extends Entity { + return ((Byte) this.datawatcher.get(EntityLiving.ag) & 2) > 0 ? EnumHand.OFF_HAND : EnumHand.MAIN_HAND; + } + ++ // Paper start - lag compensate eating ++ protected long eatStartTime; ++ protected int totalEatTimeTicks; ++ // Paper end ++ + private void t() { + if (this.isHandRaised()) { + if (ItemStack.d(this.b(this.getRaisedHand()), this.activeItem)) { +@@ -3160,7 +3165,12 @@ public abstract class EntityLiving extends Entity { + this.b(this.activeItem, 5); + } + +- if (--this.bd == 0 && !this.world.isClientSide && !this.activeItem.m()) { ++ // Paper start - lag compensate eating ++ // we add 1 to the expected time to avoid lag compensating when we should not ++ boolean shouldLagCompensate = this.activeItem.getItem().isFood() && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1 + this.totalEatTimeTicks) * 50 * (1000 * 1000)); ++ if ((--this.bd == 0 || shouldLagCompensate) && !this.world.isClientSide && !this.activeItem.m()) { ++ this.setEatTimeTicks(0); ++ // Paper end + this.s(); + } + } else { +@@ -3210,7 +3220,10 @@ public abstract class EntityLiving extends Entity { + + if (!itemstack.isEmpty() && !this.isHandRaised() || forceUpdate) { // Paper use override flag + this.activeItem = itemstack; +- this.bd = itemstack.k(); ++ // Paper start - lag compensate eating ++ this.bd = this.totalEatTimeTicks = itemstack.k(); ++ this.eatStartTime = System.nanoTime(); ++ // Paper end + if (!this.world.isClientSide) { + this.c(1, true); + this.c(2, enumhand == EnumHand.OFF_HAND); +@@ -3234,7 +3247,10 @@ public abstract class EntityLiving extends Entity { + } + } else if (!this.isHandRaised() && !this.activeItem.isEmpty()) { + this.activeItem = ItemStack.b; +- this.bd = 0; ++ // Paper start - lag compensate eating ++ this.bd = this.totalEatTimeTicks = 0; ++ this.eatStartTime = -1L; ++ // Paper end + } + } + +@@ -3362,7 +3378,10 @@ public abstract class EntityLiving extends Entity { + } + + this.activeItem = ItemStack.b; +- this.bd = 0; ++ // Paper start - lag compensate eating ++ this.bd = this.totalEatTimeTicks = 0; ++ this.eatStartTime = -1L; ++ // Paper end + } + + public boolean isBlocking() { diff --git a/patches/server-unmapped/0001/0399-Optimize-call-to-getFluid-for-explosions.patch b/patches/server-unmapped/0001/0399-Optimize-call-to-getFluid-for-explosions.patch new file mode 100644 index 0000000000..aa68e86bd8 --- /dev/null +++ b/patches/server-unmapped/0001/0399-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 618cf4e0d71b4b04085807314e79a02785f8a498..a9ecc2b4da587ca3d3c99f8c8af38092a02fb572 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -153,7 +153,7 @@ public class Explosion { + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPosition blockposition = new BlockPosition(d4, d5, d6); + IBlockData iblockdata = this.world.getType(blockposition); +- Fluid fluid = this.world.getFluid(blockposition); ++ Fluid fluid = iblockdata.getFluid(); // Paper + Optional optional = this.l.a(this, this.world, blockposition, iblockdata, fluid); + + if (optional.isPresent()) { diff --git a/patches/server-unmapped/0001/0400-Fix-last-firework-in-stack-not-having-effects-when-d.patch b/patches/server-unmapped/0001/0400-Fix-last-firework-in-stack-not-having-effects-when-d.patch new file mode 100644 index 0000000000..f288956882 --- /dev/null +++ b/patches/server-unmapped/0001/0400-Fix-last-firework-in-stack-not-having-effects-when-d.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 17 Jan 2020 18:44:55 -0800 +Subject: [PATCH] Fix last firework in stack not having effects when dispensed + - #2871 + +CB used the resulting item in the dispenser rather than the item +dispensed. The resulting item would have size == 0 and therefore +be convertered to air, hence why the effects disappeared. + +diff --git a/src/main/java/net/minecraft/core/dispenser/IDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/IDispenseBehavior.java +index 158075319bd49ac78ea994639cdad21aeacdf86f..93093c05da53e5ddc59fac179081af2e0893706d 100644 +--- a/src/main/java/net/minecraft/core/dispenser/IDispenseBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/IDispenseBehavior.java +@@ -426,7 +426,7 @@ public interface IDispenseBehavior { + } + + itemstack1 = CraftItemStack.asNMSCopy(event.getItem()); +- EntityFireworks entityfireworks = new EntityFireworks(isourceblock.getWorld(), itemstack, isourceblock.getX(), isourceblock.getY(), isourceblock.getX(), true); ++ EntityFireworks entityfireworks = new EntityFireworks(isourceblock.getWorld(), itemstack1, isourceblock.getX(), isourceblock.getY(), isourceblock.getX(), true); // Paper - GH-2871 - fix last firework in stack having no effects when dispensed + + IDispenseBehavior.a(isourceblock, entityfireworks, enumdirection); + entityfireworks.shoot((double) enumdirection.getAdjacentX(), (double) enumdirection.getAdjacentY(), (double) enumdirection.getAdjacentZ(), 0.5F, 1.0F); diff --git a/patches/server-unmapped/0001/0401-Add-effect-to-block-break-naturally.patch b/patches/server-unmapped/0001/0401-Add-effect-to-block-break-naturally.patch new file mode 100644 index 0000000000..e1c6127f7b --- /dev/null +++ b/patches/server-unmapped/0001/0401-Add-effect-to-block-break-naturally.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 2 Jan 2020 12:25:07 -0600 +Subject: [PATCH] Add effect to block break naturally + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 724b230259b1b44bc9fdde6c4fcbcdde5f690e05..e3ab0b76e5003553b29215a43bc5a762f2663648 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -633,6 +633,13 @@ public class CraftBlock implements Block { + + @Override + public boolean breakNaturally(ItemStack item) { ++ // Paper start ++ return breakNaturally(item, false); ++ } ++ ++ @Override ++ public boolean breakNaturally(ItemStack item, boolean triggerEffect) { ++ // Paper end + // Order matters here, need to drop before setting to air so skulls can get their data + net.minecraft.world.level.block.state.IBlockData iblockdata = this.getNMS(); + net.minecraft.world.level.block.Block block = iblockdata.getBlock(); +@@ -642,6 +649,7 @@ public class CraftBlock implements Block { + // Modelled off EntityHuman#hasBlock + if (block != Blocks.AIR && (item == null || !iblockdata.isRequiresSpecialTool() || nmsItem.canDestroySpecialBlock(iblockdata))) { + net.minecraft.world.level.block.Block.dropItems(iblockdata, world.getMinecraftWorld(), position, world.getTileEntity(position), null, nmsItem); ++ if (triggerEffect) world.triggerEffect(org.bukkit.Effect.STEP_SOUND.getId(), position, net.minecraft.world.level.block.Block.getCombinedId(block.getBlockData())); // Paper + result = true; + } + diff --git a/patches/server-unmapped/0001/0402-Tracking-Range-Improvements.patch b/patches/server-unmapped/0001/0402-Tracking-Range-Improvements.patch new file mode 100644 index 0000000000..2e20c11638 --- /dev/null +++ b/patches/server-unmapped/0001/0402-Tracking-Range-Improvements.patch @@ -0,0 +1,67 @@ +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/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 9c5b1dd305567f09a23a3f189d4dadba323b643e..4be5f3be285b1944eee66684c1a565ac1eceb024 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -1789,6 +1789,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); + int j = entity.getEntityType().getChunkRange() * 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 0449451142d59e828a32fe237751c7d8484894a2..3277a8aaffb6a25624967aa0c62f61309a517739 100644 +--- a/src/main/java/org/spigotmc/TrackingRange.java ++++ b/src/main/java/org/spigotmc/TrackingRange.java +@@ -25,26 +25,26 @@ public class TrackingRange + if ( entity instanceof EntityPlayer ) + { + return config.playerTrackingRange; +- } else if ( entity.activationType == ActivationRange.ActivationType.MONSTER || entity.activationType == ActivationRange.ActivationType.RAIDER ) +- { +- return config.monsterTrackingRange; +- } else if ( entity instanceof EntityGhast ) +- { +- 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 EntityItemFrame || entity instanceof EntityPainting || entity instanceof EntityItem || entity instanceof EntityExperienceOrb ) ++ case WATER: ++ case VILLAGER: ++ case ANIMAL: ++ return config.animalTrackingRange; ++ case MISC: ++ } ++ if ( entity instanceof EntityItemFrame || entity instanceof EntityPainting || entity instanceof EntityItem || entity instanceof EntityExperienceOrb ) ++ // Paper end + { + return config.miscTrackingRange; + } else + { ++ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon) return ((net.minecraft.server.level.WorldServer)(entity.getWorld())).getChunkProvider().playerChunkMap.getLoadViewDistance(); // Paper - enderdragon is exempt + return config.otherTrackingRange; + } + } diff --git a/patches/server-unmapped/0001/0403-Entity-Activation-Range-2.0.patch b/patches/server-unmapped/0001/0403-Entity-Activation-Range-2.0.patch new file mode 100644 index 0000000000..2257602b05 --- /dev/null +++ b/patches/server-unmapped/0001/0403-Entity-Activation-Range-2.0.patch @@ -0,0 +1,869 @@ +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 + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 07b2f7f13044ace4db48274da8309c37aaa2d2c4..18169d48598b873b0d7507bb55a1a0bad0ab6566 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -861,17 +861,17 @@ public class WorldServer extends World implements GeneratorAccessSeed { + ++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.ticksLived++; + timer = entity.getEntityType().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.getEntityType().passengerTickTimer.startTiming() : entity.getEntityType().tickTimer.startTiming(); ++ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); ++ timer = isActive ? entity.getEntityType().tickTimer.startTiming() : entity.getEntityType().inactiveTickTimer.startTiming(); // Paper + try { + // Paper end - timings + entity.g(entity.locX(), entity.locY(), entity.locZ()); +@@ -885,12 +885,16 @@ public class WorldServer extends World implements GeneratorAccessSeed { + return IRegistry.ENTITY_TYPE.getKey(entity.getEntityType()).toString(); + }); + gameprofilerfiller.c("tickNonPassenger"); ++ if (isActive) { // Paper - EAR 2 ++ TimingHistory.activatedEntityTicks++; // Paper + entity.tick(); + entity.postTick(); // CraftBukkit ++ } else { entity.inactiveTick(); } // Paper - EAR 2 + gameprofilerfiller.exit(); + } + + this.chunkCheck(entity); ++ } finally { timer.stopTiming(); } // Paper - timings + if (entity.inChunk) { + Iterator iterator = entity.getPassengers().iterator(); + +@@ -900,7 +904,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.a(entity, entity1); + } + } +- } finally { timer.stopTiming(); } // Paper - timings ++ //} finally { timer.stopTiming(); } // Paper - timings - move up + + } + } +@@ -908,6 +912,11 @@ public class WorldServer extends World implements GeneratorAccessSeed { + public void a(Entity entity, Entity entity1) { + if (!entity1.dead && entity1.getVehicle() == entity) { + if (entity1 instanceof EntityHuman || this.getChunkProvider().a(entity1)) { ++ // Paper - EAR 2 ++ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity1); ++ co.aikar.timings.Timing timer = isActive ? entity1.getEntityType().passengerTickTimer.startTiming() : entity1.getEntityType().passengerInactiveTickTimer.startTiming(); // Paper ++ try { ++ // Paper end + entity1.g(entity1.locX(), entity1.locY(), entity1.locZ()); + entity1.lastYaw = entity1.yaw; + entity1.lastPitch = entity1.pitch; +@@ -919,8 +928,17 @@ public class WorldServer extends World implements GeneratorAccessSeed { + return IRegistry.ENTITY_TYPE.getKey(entity1.getEntityType()).toString(); + }); + gameprofilerfiller.c("tickPassenger"); ++ // Paper start - EAR 2 ++ if (isActive) { + entity1.passengerTick(); + entity1.postTick(); // CraftBukkit ++ } else { ++ entity1.setMot(Vec3D.ORIGIN); ++ entity1.inactiveTick(); ++ // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary ++ entity.syncPositionOf(entity1); ++ } ++ // Paper end - EAR 2 + gameprofilerfiller.exit(); + } + +@@ -933,7 +951,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + this.a(entity1, entity2); + } +- } ++ } } finally { timer.stopTiming(); } // Paper - EAR2 timings + + } + } else { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5acf61ece9ca38a262387fd0395bd464312501fd..bc136276cad4e87d8658072b2f62f608670f39ca 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -68,6 +68,7 @@ import net.minecraft.world.entity.animal.EntityFish; + import net.minecraft.world.entity.item.EntityItem; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.entity.vehicle.EntityBoat; ++import net.minecraft.world.entity.vehicle.EntityMinecartAbstract; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.enchantment.EnchantmentManager; + import net.minecraft.world.item.enchantment.EnchantmentProtection; +@@ -251,7 +252,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + public boolean Y; + public boolean impulse; + public int portalCooldown; +- protected boolean inPortal; ++ public boolean inPortal; // Paper - public + protected int portalTicks; + protected BlockPosition ac; + private boolean invulnerable; +@@ -275,6 +276,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; + public long activatedTick = Integer.MIN_VALUE; ++ public boolean isTemporarilyActive = false; // Paper + public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one + protected int numCollisions = 0; // Paper + public void inactiveTick() { } +@@ -665,6 +667,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + this.recalcPosition(); + } else { + if (enummovetype == EnumMoveType.PISTON) { ++ this.activatedTick = MinecraftServer.currentTick + 20; // Paper + vec3d = this.b(vec3d); + if (vec3d.equals(Vec3D.ORIGIN)) { + return; +@@ -677,6 +680,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + this.x = Vec3D.ORIGIN; + this.setMot(Vec3D.ORIGIN); + } ++ // Paper start - ignore movement changes while inactive. ++ if (isTemporarilyActive && !(this instanceof EntityItem || this instanceof EntityMinecartAbstract) && vec3d == getMot() && enummovetype == EnumMoveType.SELF) { ++ setMot(Vec3D.ORIGIN); ++ this.world.getMethodProfiler().exit(); ++ return; ++ } ++ // Paper end + + vec3d = this.a(vec3d, enummovetype); + Vec3D vec3d1 = this.g(vec3d); +@@ -2007,6 +2017,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + } + ++ public void syncPositionOf(Entity entity) { k(entity); } // Paper - OBFHELPER + public void k(Entity entity) { + this.a(entity, Entity::setPosition); + } +@@ -2817,6 +2828,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return this.ae; + } + ++ public final boolean isPushedByWater() { return this.bV(); } // Paper - OBFHELPER - the below is not an obfhelper, don't use it! + public boolean bV() { + // Paper start + return this.pushedByWater(); +diff --git a/src/main/java/net/minecraft/world/entity/EntityCreature.java b/src/main/java/net/minecraft/world/entity/EntityCreature.java +index a9322e7cd8e07a2d5578c861991d53ec85fbfbcc..bbf0f345bfdd8a3a1f7fe902a42b2b18cdcf07a5 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityCreature.java ++++ b/src/main/java/net/minecraft/world/entity/EntityCreature.java +@@ -14,6 +14,7 @@ import org.bukkit.event.entity.EntityUnleashEvent; + public abstract class EntityCreature extends EntityInsentient { + + public org.bukkit.craftbukkit.entity.CraftCreature getBukkitCreature() { return (org.bukkit.craftbukkit.entity.CraftCreature) super.getBukkitEntity(); } // Paper ++ public BlockPosition movingTarget = null; public BlockPosition getMovingTarget() { return movingTarget; } // Paper + + protected EntityCreature(EntityTypes entitytypes, World world) { + super(entitytypes, world); +diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +index db72b685f4a4b95f345f1d34f9eeb83b8731120a..89d24d7532a256434513a45c901946e28db396bd 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java ++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +@@ -115,7 +115,7 @@ public abstract class EntityInsentient extends EntityLiving { + public MinecraftKey lootTableKey; + public long lootTableSeed; + @Nullable +- private Entity leashHolder; ++ public Entity leashHolder; // Paper - private -> public + private int bx; + @Nullable + private NBTTagCompound by; +@@ -196,6 +196,19 @@ public abstract class EntityInsentient extends EntityLiving { + return this.lookController; + } + ++ // Paper start ++ @Override ++ public void inactiveTick() { ++ super.inactiveTick(); ++ if (this.goalSelector.inactiveTick()) { ++ this.goalSelector.doTick(); ++ } ++ if (this.targetSelector.inactiveTick()) { ++ this.targetSelector.doTick(); ++ } ++ } ++ // Paper end ++ + public ControllerMove getControllerMove() { + if (this.isPassenger() && this.getVehicle() instanceof EntityInsentient) { + EntityInsentient entityinsentient = (EntityInsentient) this.getVehicle(); +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index f851a9806e3b936093275cf404caca82c6662ab4..02d3b792cc9769b5daa6fcac57f5cda320a2a29e 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -192,7 +192,7 @@ public abstract class EntityLiving extends Entity { + protected float aN; + protected int aO;protected int getKillCount() { return this.aO; } // Paper - OBFHELPER + public float lastDamage; +- protected boolean jumping; ++ public boolean jumping; // Paper protected -> public + public float aR; + public float aS; + public float aT; +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java +index 5e599d88a150c907f50acbb58ad1725c3fe361e4..b505c23c57a4b84faf5906c6295455b4720c4426 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java +@@ -20,7 +20,10 @@ public abstract class PathfinderGoal { + + public void c() {} + +- public void d() {} ++ public void d() { ++ onTaskReset(); // Paper ++ } ++ public void onTaskReset() {} // Paper + + public void e() {} + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalGotoTarget.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalGotoTarget.java +index 6a156a488bc073b3b60f4d1081e3f2ab65ba9e96..8a0515ae03c9081b03e9c2a312826345038b8fa7 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalGotoTarget.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalGotoTarget.java +@@ -9,12 +9,12 @@ import net.minecraft.world.level.IWorldReader; + + public abstract class PathfinderGoalGotoTarget extends PathfinderGoal { + +- protected final EntityCreature a; ++ protected final EntityCreature a;public EntityCreature getEntity() { return a; } // Paper - OBFHELPER + public final double b; + protected int c; + protected int d; + private int g; +- protected BlockPosition e;public final BlockPosition getTargetPosition() { return this.e; } // Paper - OBFHELPER ++ protected BlockPosition e; public final BlockPosition getTargetPosition() { return this.e; } public void setTargetPosition(BlockPosition pos) { this.e = pos; getEntity().movingTarget = pos != BlockPosition.ZERO ? pos : null; } // Paper - OBFHELPER + private boolean h; + private final int i; + private final int j; +@@ -23,6 +23,13 @@ public abstract class PathfinderGoalGotoTarget extends PathfinderGoal { + public PathfinderGoalGotoTarget(EntityCreature entitycreature, double d0, int i) { + this(entitycreature, d0, i, 1); + } ++ // Paper start - activation range improvements ++ @Override ++ public void onTaskReset() { ++ super.onTaskReset(); ++ setTargetPosition(BlockPosition.ZERO); ++ } ++ // Paper end + + public PathfinderGoalGotoTarget(EntityCreature entitycreature, double d0, int i, int j) { + this.e = BlockPosition.ZERO; +@@ -111,6 +118,7 @@ public abstract class PathfinderGoalGotoTarget extends PathfinderGoal { + blockposition_mutableblockposition.a((BaseBlockPosition) blockposition, i1, k - 1, j1); + if (this.a.a((BlockPosition) blockposition_mutableblockposition) && this.a(this.a.world, blockposition_mutableblockposition)) { + this.e = blockposition_mutableblockposition; ++ setTargetPosition(blockposition_mutableblockposition.immutableCopy()); // Paper + return true; + } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java +index c9aaa63fcb0abe5628798827003c677c883c2a18..8c234c09a4d9ada83e36e3cdbcc1f2f5c6202f28 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java +@@ -26,10 +26,11 @@ public class PathfinderGoalSelector { + } + }; + private final Map c = new EnumMap(PathfinderGoal.Type.class); +- private final Set d = Sets.newLinkedHashSet(); ++ private final Set d = Sets.newLinkedHashSet(); private Set getTasks() { return d; }// Paper - OBFHELPER + private final Supplier e; + private final EnumSet f = EnumSet.noneOf(PathfinderGoal.Type.class); +- private int g = 3; ++ private int g = 3;private int getTickRate() { return g; } // Paper - OBFHELPER ++ private int curRate;private int getCurRate() { return curRate; } private void incRate() { this.curRate++; } // Paper TODO + + public PathfinderGoalSelector(Supplier supplier) { + this.e = supplier; +@@ -39,6 +40,21 @@ public class PathfinderGoalSelector { + this.d.add(new PathfinderGoalWrapped(i, pathfindergoal)); + } + ++ // Paper start ++ public boolean inactiveTick() { ++ incRate(); ++ return getCurRate() % getTickRate() == 0; ++ } ++ public boolean hasTasks() { ++ for (PathfinderGoalWrapped task : getTasks()) { ++ if (task.isRunning()) { ++ return true; ++ } ++ } ++ return false; ++ } ++ // Paper end ++ + public void a(PathfinderGoal pathfindergoal) { + this.d.stream().filter((pathfindergoalwrapped) -> { + return pathfindergoalwrapped.j() == pathfindergoal; +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java +index 7395335ee97237376d34e315ea1d7d46766b278a..7bb531e47668cf445083c4dedb03ccafe6a9c96b 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java +@@ -64,6 +64,7 @@ public class PathfinderGoalWrapped extends PathfinderGoal { + return this.a.i(); + } + ++ public boolean isRunning() { return this.g(); } // Paper - OBFHELPER + public boolean g() { + return this.c; + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/EntityLlama.java b/src/main/java/net/minecraft/world/entity/animal/horse/EntityLlama.java +index 8b09aaa30dd753fd34bea155890bdd9e5cb180f5..2005cb484ba6b5929ad81d3d120521f247f3d4cf 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/EntityLlama.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/EntityLlama.java +@@ -454,6 +454,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + return this.bC != null; + } + ++ public final boolean inCaravan() { return this.fC(); } // Paper - OBFHELPER + public boolean fC() { + return this.bB != null; + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +index 534efe39beee393d11705b8f0b13ce4ca727c3eb..07f87ee8f5df9d7a40001dd28f50457344308a03 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +@@ -213,17 +213,29 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + @Override + public void inactiveTick() { + // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :( +- if (world.spigotConfig.tickInactiveVillagers && this.doAITick()) { +- this.mobTick(); ++ // Paper start ++ if (this.getUnhappy() > 0) { ++ this.setUnhappy(this.getUnhappy() - 1); + } ++ if (this.doAITick()) { ++ if (world.spigotConfig.tickInactiveVillagers) { ++ this.mobTick(); ++ } else { ++ this.mobTick(true); ++ } ++ } ++ doReputationTick(); ++ // Paper end ++ + super.inactiveTick(); + } + // Spigot End + + @Override +- protected void mobTick() { ++ protected void mobTick() { mobTick(false); } ++ protected void mobTick(boolean inactive) { + this.world.getMethodProfiler().enter("villagerBrain"); +- this.getBehaviorController().a((WorldServer) this.world, this); // CraftBukkit - decompile error ++ if (!inactive) this.getBehaviorController().a((WorldServer) this.world, this); // CraftBukkit - decompile error // Paper + this.world.getMethodProfiler().exit(); + if (this.bF) { + this.bF = false; +@@ -247,7 +259,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + this.bv = null; + } + +- if (!this.isNoAI() && this.random.nextInt(100) == 0) { ++ if (!inactive && !this.isNoAI() && this.random.nextInt(100) == 0) { // Paper + Raid raid = ((WorldServer) this.world).b_(this.getChunkCoordinates()); + + if (raid != null && raid.v() && !raid.a()) { +@@ -258,6 +270,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + if (this.getVillagerData().getProfession() == VillagerProfession.NONE && this.eN()) { + this.eT(); + } ++ if (inactive) return; // Paper + + super.mobTick(); + } +@@ -894,6 +907,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + } + } + ++ private void doReputationTick() { fw(); } // Paper - OBFHELPER + private void fw() { + long i = this.world.getTime(); + +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java +index 86340c0f505da0d6b02f385bb44f5a5d2fc2ff43..3e7c6416d1d812661a6f24920573cad1a0ca2503 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java +@@ -72,10 +72,12 @@ public abstract class EntityVillagerAbstract extends EntityAgeable implements NP + return super.prepare(worldaccess, difficultydamagescaler, enummobspawn, (GroupDataEntity) groupdataentity, nbttagcompound); + } + ++ public final int getUnhappy() { return eK(); } // Paper - OBFHELPER + public int eK() { + return (Integer) this.datawatcher.get(EntityVillagerAbstract.bp); + } + ++ public final void setUnhappy(int i) { s(i); } // Paper - OBFHELPER + public void s(int i) { + this.datawatcher.set(EntityVillagerAbstract.bp, i); + } +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index d1738b57efd3f5e6c51603553a773173e4b09bb5..c4680142bf23d30169555abe7db78d85811e042b 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -142,6 +142,12 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public long ticksPerWaterSpawns; + public long ticksPerWaterAmbientSpawns; + public long ticksPerAmbientSpawns; ++ // 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 + +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 53d0541aba207b5eaea2e49edbb56df918d30333..663127e6e6ec507959142b18a11a5a4790d4b98b 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -31,11 +31,30 @@ import net.minecraft.world.level.chunk.Chunk; + import net.minecraft.world.phys.AxisAlignedBB; + import co.aikar.timings.MinecraftTimings; + ++// Paper start ++import net.minecraft.core.BlockPosition; ++import net.minecraft.server.level.ChunkProviderServer; ++import net.minecraft.world.entity.EntityFlying; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.ai.BehaviorController; ++import net.minecraft.world.entity.animal.EntityBee; ++import net.minecraft.world.entity.animal.EntityWaterAnimal; ++import net.minecraft.world.entity.animal.horse.EntityLlama; ++import net.minecraft.world.entity.monster.EntityPillager; ++import net.minecraft.world.entity.monster.IMonster; ++import net.minecraft.world.entity.schedule.Activity; ++import net.minecraft.world.entity.item.EntityFallingBlock; ++import net.minecraft.world.entity.projectile.EntityEnderSignal; ++// Paper end ++ + public class ActivationRange + { + + public enum ActivationType + { ++ WATER, // Paper ++ FLYING_MONSTER, // Paper ++ VILLAGER, // Paper + MONSTER, + ANIMAL, + RAIDER, +@@ -43,6 +62,43 @@ public class ActivationRange + + AxisAlignedBB boundingBox = new AxisAlignedBB( 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) { ++ World world = entity.world; ++ 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 AxisAlignedBB maxBB = new AxisAlignedBB( 0, 0, 0, 0, 0, 0 ); + +@@ -55,10 +111,13 @@ public class ActivationRange + */ + public static ActivationType initializeEntityActivationType(Entity entity) + { ++ if (entity instanceof EntityWaterAnimal) { return ActivationType.WATER; } // Paper ++ else if (entity instanceof EntityVillager) { return ActivationType.VILLAGER; } // Paper ++ else if (entity instanceof EntityFlying && entity instanceof IMonster) { return ActivationType.FLYING_MONSTER; } // Paper - doing & Monster incase Flying no longer includes monster in future + if ( entity instanceof EntityRaider ) + { + return ActivationType.RAIDER; +- } else if ( entity instanceof EntityMonster || entity instanceof EntitySlime ) ++ } else if ( entity instanceof IMonster ) // Paper - correct monster check + { + return ActivationType.MONSTER; + } else if ( entity instanceof EntityCreature || entity instanceof EntityAmbient ) +@@ -79,10 +138,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 EntityEnderSignal // Paper + || entity instanceof EntityHuman + || entity instanceof EntityProjectile + || entity instanceof EntityEnderDragon +@@ -115,10 +178,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 ChunkProviderServer chunkProvider = (ChunkProviderServer) world.getChunkProvider(); ++ // 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.viewDistance << 4 ) - 8, maxRange ); + + for ( EntityHuman player : world.getPlayers() ) +@@ -130,6 +208,11 @@ public class ActivationRange + ActivationType.RAIDER.boundingBox = player.getBoundingBox().grow( raiderActivationRange, 256, raiderActivationRange ); + ActivationType.ANIMAL.boundingBox = player.getBoundingBox().grow( animalActivationRange, 256, animalActivationRange ); + ActivationType.MONSTER.boundingBox = player.getBoundingBox().grow( monsterActivationRange, 256, monsterActivationRange ); ++ // Paper start ++ ActivationType.WATER.boundingBox = player.getBoundingBox().grow( waterActivationRange, 256, waterActivationRange ); ++ ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().grow( flyingActivationRange, 256, flyingActivationRange ); ++ ActivationType.VILLAGER.boundingBox = player.getBoundingBox().grow( villagerActivationRange, 256, waterActivationRange ); ++ // Paper end + + int i = MathHelper.floor( maxBB.minX / 16.0D ); + int j = MathHelper.floor( maxBB.maxX / 16.0D ); +@@ -140,7 +223,7 @@ public class ActivationRange + { + for ( int j1 = k; j1 <= l; ++j1 ) + { +- Chunk chunk = (Chunk) world.getChunkIfLoadedImmediately( i1, j1 ); ++ Chunk chunk = chunkProvider.getChunkAtIfLoadedMainThreadNoCache( i1, j1 ); // Paper + if ( chunk != null ) + { + activateChunkEntities( chunk ); +@@ -158,19 +241,15 @@ public class ActivationRange + */ + private static void activateChunkEntities(Chunk chunk) + { +- for ( java.util.List slice : chunk.entitySlices ) +- { +- for ( Entity entity : (Collection) slice ) ++ // Paper start ++ Entity[] rawData = chunk.entities.getRawData(); ++ for (int i = 0; i < chunk.entities.size(); i++) { ++ Entity entity = rawData[i]; ++ //for ( Entity entity : (Collection) slice ) ++ // Paper end + { +- if ( MinecraftServer.currentTick > entity.activatedTick ) +- { +- if ( entity.defaultActivationState ) +- { +- entity.activatedTick = MinecraftServer.currentTick; +- continue; +- } +- if ( entity.activationType.boundingBox.c( entity.getBoundingBox() ) ) +- { ++ if (MinecraftServer.currentTick > entity.activatedTick) { ++ if (entity.defaultActivationState || entity.activationType.boundingBox.c(entity.getBoundingBox())) { // Paper + entity.activatedTick = MinecraftServer.currentTick; + } + } +@@ -185,56 +264,105 @@ 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.world.spigotConfig; ++ int inactiveWakeUpImmunity = checkInactiveWakeup(entity); ++ if (inactiveWakeUpImmunity > -1) { ++ return inactiveWakeUpImmunity; ++ } ++ if (entity.fireTicks > 0) { ++ return 2; ++ } ++ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; ++ // Paper end + // quick checks. +- if ( entity.inWater || entity.fireTicks > 0 ) ++ if ( (entity.activationType != ActivationType.WATER && entity.inWater && entity.isPushedByWater()) ) // Paper + { +- return true; ++ return 100; // Paper + } + if ( !( entity instanceof EntityArrow ) ) + { +- if ( !entity.isOnGround() || !entity.passengers.isEmpty() || entity.isPassenger() ) ++ if ( (!entity.isOnGround() && !(entity instanceof EntityFlying)) ) // Paper - remove passengers logic + { +- return true; ++ return 10; // Paper + } + } else if ( !( (EntityArrow) entity ).inGround ) + { +- return true; ++ return 1; // Paper + } + // special cases. + if ( entity instanceof EntityLiving ) + { + EntityLiving living = (EntityLiving) entity; +- if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTicks > 0 || living.effects.size() > 0 ) ++ if ( living.isClimbing() || living.jumping || living.hurtTicks > 0 || living.effects.size() > 0 ) // Paper + { +- return true; ++ return 1; // Paper + } +- if ( entity instanceof EntityCreature && ( (EntityCreature) entity ).getGoalTarget() != null ) ++ if ( entity instanceof EntityInsentient && ((EntityInsentient) entity ).getGoalTarget() != null) // Paper + { +- return true; ++ return 20; // Paper + } +- if ( entity instanceof EntityVillager && ( (EntityVillager) entity ).canBreed() ) ++ // Paper start ++ if (entity instanceof EntityBee) { ++ EntityBee bee = (EntityBee)entity; ++ BlockPosition movingTarget = bee.getMovingTarget(); ++ if (bee.isAngry() || ++ (bee.getHivePos() != null && bee.getHivePos().equals(movingTarget)) || ++ (bee.getFlowerPos() != null && bee.getFlowerPos().equals(movingTarget)) ++ ) { ++ return 20; ++ } ++ } ++ if ( entity instanceof EntityVillager ) { ++ BehaviorController behaviorController = ((EntityVillager) entity).getBehaviorController(); ++ ++ if (config.villagersActiveForPanic) { ++ for (Activity activity : VILLAGER_PANIC_IMMUNITIES) { ++ if (behaviorController.c(activity)) { ++ return 20*5; ++ } ++ } ++ } ++ ++ if (config.villagersWorkImmunityAfter > 0 && inactiveFor >= config.villagersWorkImmunityAfter) { ++ if (behaviorController.c(Activity.WORK)) { ++ return config.villagersWorkImmunityFor; ++ } ++ } ++ } ++ if ( entity instanceof EntityLlama && ( (EntityLlama) entity ).inCaravan() ) + { +- return true; ++ return 1; + } ++ // Paper end + if ( entity instanceof EntityAnimal ) + { + EntityAnimal animal = (EntityAnimal) entity; + if ( animal.isBaby() || animal.isInLove() ) + { +- return true; ++ return 5; // Paper + } + if ( entity instanceof EntitySheep && ( (EntitySheep) entity ).isSheared() ) + { +- return true; ++ return 1; // Paper + } + } + if (entity instanceof EntityCreeper && ((EntityCreeper) entity).isIgnited()) { // isExplosive +- return true; ++ return 20; // Paper + } ++ // Paper start ++ if (entity instanceof EntityInsentient && ((EntityInsentient) entity).targetSelector.hasTasks() ) { ++ return 0; ++ } ++ if (entity instanceof EntityPillager) { ++ EntityPillager pillager = (EntityPillager) entity; ++ // TODO:? ++ } ++ // Paper end + } +- return false; ++ return -1; // Paper + } + + /** +@@ -249,8 +377,19 @@ public class ActivationRange + if ( !entity.inChunk || entity instanceof EntityFireworks ) { + return true; + } ++ // Paper start - special case always immunities ++ // immunize brand new entities, dead entities, and portal scenarios ++ if (entity.defaultActivationState || entity.ticksLived < 20*10 || !entity.isAlive() || entity.inPortal || entity.portalCooldown > 0) { ++ return true; ++ } ++ // immunize leashed entities ++ if (entity instanceof EntityInsentient && ((EntityInsentient)entity).leashHolder instanceof EntityHuman) { ++ 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 ) +@@ -258,15 +397,19 @@ public class ActivationRange + if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 ) + { + // Check immunities every 20 ticks. +- if ( 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.ticksLived % 4 == 0 && !checkEntityImmunities( entity ) ) ++ } else if (entity.ticksLived % 4 == 0 && 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 34ee684901906fc2ef5f0d09680d2686b813e52b..6b015c1f26facb4e82d75b252164dec05731ca6c 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -180,13 +180,59 @@ 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; + private void activationRange() + { ++ boolean hasAnimalsConfig = config.getInt("entity-activation-range.animals", animalActivationRange) != animalActivationRange; // Paper + animalActivationRange = getInt( "entity-activation-range.animals", animalActivationRange ); + monsterActivationRange = getInt( "entity-activation-range.monsters", monsterActivationRange ); + raiderActivationRange = getInt( "entity-activation-range.raiders", raiderActivationRange ); + miscActivationRange = getInt( "entity-activation-range.misc", miscActivationRange ); ++ // Paper start ++ waterActivationRange = getInt( "entity-activation-range.water", waterActivationRange ); ++ villagerActivationRange = getInt( "entity-activation-range.villagers", hasAnimalsConfig ? animalActivationRange : villagerActivationRange ); ++ flyingMonsterActivationRange = getInt( "entity-activation-range.flying-monsters", flyingMonsterActivationRange ); ++ ++ wakeUpInactiveAnimals = getInt("entity-activation-range.wake-up-inactive.animals-max-per-tick", wakeUpInactiveAnimals); ++ wakeUpInactiveAnimalsEvery = getInt("entity-activation-range.wake-up-inactive.animals-every", wakeUpInactiveAnimalsEvery); ++ wakeUpInactiveAnimalsFor = getInt("entity-activation-range.wake-up-inactive.animals-for", wakeUpInactiveAnimalsFor); ++ ++ wakeUpInactiveMonsters = getInt("entity-activation-range.wake-up-inactive.monsters-max-per-tick", wakeUpInactiveMonsters); ++ wakeUpInactiveMonstersEvery = getInt("entity-activation-range.wake-up-inactive.monsters-every", wakeUpInactiveMonstersEvery); ++ wakeUpInactiveMonstersFor = getInt("entity-activation-range.wake-up-inactive.monsters-for", wakeUpInactiveMonstersFor); ++ ++ wakeUpInactiveVillagers = getInt("entity-activation-range.wake-up-inactive.villagers-max-per-tick", wakeUpInactiveVillagers); ++ wakeUpInactiveVillagersEvery = getInt("entity-activation-range.wake-up-inactive.villagers-every", wakeUpInactiveVillagersEvery); ++ wakeUpInactiveVillagersFor = getInt("entity-activation-range.wake-up-inactive.villagers-for", wakeUpInactiveVillagersFor); ++ ++ wakeUpInactiveFlying = getInt("entity-activation-range.wake-up-inactive.flying-monsters-max-per-tick", wakeUpInactiveFlying); ++ wakeUpInactiveFlyingEvery = getInt("entity-activation-range.wake-up-inactive.flying-monsters-every", wakeUpInactiveFlyingEvery); ++ wakeUpInactiveFlyingFor = getInt("entity-activation-range.wake-up-inactive.flying-monsters-for", wakeUpInactiveFlyingFor); ++ ++ villagersWorkImmunityAfter = getInt( "entity-activation-range.villagers-work-immunity-after", villagersWorkImmunityAfter ); ++ villagersWorkImmunityFor = getInt( "entity-activation-range.villagers-work-immunity-for", villagersWorkImmunityFor ); ++ villagersActiveForPanic = getBoolean( "entity-activation-range.villagers-active-for-panic", villagersActiveForPanic ); ++ // Paper end + tickInactiveVillagers = getBoolean( "entity-activation-range.tick-inactive-villagers", tickInactiveVillagers ); + log( "Entity Activation Range: An " + animalActivationRange + " / Mo " + monsterActivationRange + " / Ra " + raiderActivationRange + " / Mi " + miscActivationRange + " / Tiv " + tickInactiveVillagers ); + } diff --git a/patches/server-unmapped/0001/0404-Fix-items-vanishing-through-end-portal.patch b/patches/server-unmapped/0001/0404-Fix-items-vanishing-through-end-portal.patch new file mode 100644 index 0000000000..60a54b1c94 --- /dev/null +++ b/patches/server-unmapped/0001/0404-Fix-items-vanishing-through-end-portal.patch @@ -0,0 +1,28 @@ +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 bc136276cad4e87d8658072b2f62f608670f39ca..d676eaad8179cdeae410038e58ddafe0fe541ccc 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2730,6 +2730,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + BlockPosition blockposition1; + + if (flag1) { ++ // Paper start - Ensure spawn chunk is always loaded before calculating Y coordinate ++ this.world.getChunkAtWorldCoords(this.world.getSpawn()); ++ // Paper end + blockposition1 = WorldServer.a; + } else { + blockposition1 = worldserver.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING_NO_LEAVES, worldserver.getSpawn()); diff --git a/patches/server-unmapped/0001/0405-Bees-get-gravity-in-void.-Fixes-MC-167279.patch b/patches/server-unmapped/0001/0405-Bees-get-gravity-in-void.-Fixes-MC-167279.patch new file mode 100644 index 0000000000..7fbf667ed7 --- /dev/null +++ b/patches/server-unmapped/0001/0405-Bees-get-gravity-in-void.-Fixes-MC-167279.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 26 Jan 2020 16:30:19 -0600 +Subject: [PATCH] Bees get gravity in void. Fixes MC-167279 + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/control/ControllerMove.java b/src/main/java/net/minecraft/world/entity/ai/control/ControllerMove.java +index 4044861622294a317fef7e93aa86e96e8474b513..2aa5789437ba7eb20579da238c407a65a25b1d44 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/control/ControllerMove.java ++++ b/src/main/java/net/minecraft/world/entity/ai/control/ControllerMove.java +@@ -16,7 +16,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; + + public class ControllerMove { + +- protected final EntityInsentient a; ++ protected final EntityInsentient a; public final EntityInsentient getEntity() { return a; } // Paper - OBFHELPER + protected double b; + protected double c; + protected double d; +diff --git a/src/main/java/net/minecraft/world/entity/ai/control/ControllerMoveFlying.java b/src/main/java/net/minecraft/world/entity/ai/control/ControllerMoveFlying.java +index 80cba36bc59e89c40c96ca556594a4285f06fc6f..d0cbc429144b89498a7f4dc6ff64924c5ba54ad8 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/control/ControllerMoveFlying.java ++++ b/src/main/java/net/minecraft/world/entity/ai/control/ControllerMoveFlying.java +@@ -16,7 +16,7 @@ public class ControllerMoveFlying extends ControllerMove { + } + + @Override +- public void a() { ++ public void a() { tick(); } public void tick() { // Paper - OBFHELPER + if (this.h == ControllerMove.Operation.MOVE_TO) { + this.h = ControllerMove.Operation.WAIT; + this.a.setNoGravity(true); +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityBee.java b/src/main/java/net/minecraft/world/entity/animal/EntityBee.java +index 1ecf73f874f404f58a99316ae027f76db6b557db..7ce8eaeb9af3547869f467910b6a458118c63c1f 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityBee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityBee.java +@@ -111,7 +111,17 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + + public EntityBee(EntityTypes entitytypes, World world) { + super(entitytypes, world); +- this.moveController = new ControllerMoveFlying(this, 20, true); ++ // Paper start - apply gravity to bees when they get stuck in the void, fixes MC-167279 ++ this.moveController = new ControllerMoveFlying(this, 20, true) { ++ @Override ++ public void tick() { ++ if (getEntity().locY() <= 0) { ++ getEntity().setNoGravity(false); ++ } ++ super.tick(); ++ } ++ }; ++ // Paper end + this.lookController = new EntityBee.j(this); + this.a(PathType.DANGER_FIRE, -1.0F); + this.a(PathType.WATER, -1.0F); diff --git a/patches/server-unmapped/0001/0406-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/patches/server-unmapped/0001/0406-Optimise-getChunkAt-calls-for-loaded-chunks.patch new file mode 100644 index 0000000000..e84749fd43 --- /dev/null +++ b/patches/server-unmapped/0001/0406-Optimise-getChunkAt-calls-for-loaded-chunks.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 25 Jan 2020 17:04:35 -0800 +Subject: [PATCH] Optimise getChunkAt calls for loaded chunks + +bypass the need to get a player chunk, then get the either, +then unwrap it... + +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index b971082b76918fdff3bc7ebbdf6d426b04b69b26..df8428d95bbb2bbf96c524435151dfb45b61a9eb 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -470,6 +470,12 @@ public class ChunkProviderServer extends IChunkProvider { + return this.getChunkAt(i, j, chunkstatus, flag); + }, this.serverThreadQueue).join(); + } else { ++ // Paper start - optimise for loaded chunks ++ Chunk ifLoaded = this.getChunkAtIfLoadedMainThread(i, j); ++ if (ifLoaded != null) { ++ return ifLoaded; ++ } ++ // Paper end + GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); + + gameprofilerfiller.c("getChunk"); +@@ -520,39 +526,7 @@ public class ChunkProviderServer extends IChunkProvider { + if (Thread.currentThread() != this.serverThread) { + return null; + } else { +- this.world.getMethodProfiler().c("getChunkNow"); +- long k = ChunkCoordIntPair.pair(i, j); +- +- for (int l = 0; l < 4; ++l) { +- if (k == this.cachePos[l] && this.cacheStatus[l] == ChunkStatus.FULL) { +- IChunkAccess ichunkaccess = this.cacheChunk[l]; +- +- return ichunkaccess instanceof Chunk ? (Chunk) ichunkaccess : null; +- } +- } +- +- PlayerChunk playerchunk = this.getChunk(k); +- +- if (playerchunk == null) { +- return null; +- } else { +- Either either = (Either) playerchunk.b(ChunkStatus.FULL).getNow(null); // CraftBukkit - decompile error +- +- if (either == null) { +- return null; +- } else { +- IChunkAccess ichunkaccess1 = (IChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error +- +- if (ichunkaccess1 != null) { +- this.a(k, ichunkaccess1, ChunkStatus.FULL); +- if (ichunkaccess1 instanceof Chunk) { +- return (Chunk) ichunkaccess1; +- } +- } +- +- return null; +- } +- } ++ return this.getChunkAtIfLoadedMainThread(i, j); // Paper - optimise for loaded chunks + } + } + diff --git a/patches/server-unmapped/0001/0407-Allow-overriding-the-java-version-check.patch b/patches/server-unmapped/0001/0407-Allow-overriding-the-java-version-check.patch new file mode 100644 index 0000000000..3f15011d16 --- /dev/null +++ b/patches/server-unmapped/0001/0407-Allow-overriding-the-java-version-check.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 8 Feb 2020 18:02:24 -0600 +Subject: [PATCH] Allow overriding the java version check + +-DPaper.IgnoreJavaVersion=true + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 7d7b9984b867461b0a1025e5ec21ff7798281b8f..100772ba60cb7e717c5e626a4d7b44c15375b0e5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -175,7 +175,7 @@ public class Main { + float javaVersion = Float.parseFloat(System.getProperty("java.class.version")); + if (javaVersion > 60.0) { + System.err.println("Unsupported Java detected (" + javaVersion + "). Only up to Java 16 is supported."); +- return; ++ if (!Boolean.getBoolean("Paper.IgnoreJavaVersion")) return; // Paper + } + + try { diff --git a/patches/server-unmapped/0001/0408-Add-ThrownEggHatchEvent.patch b/patches/server-unmapped/0001/0408-Add-ThrownEggHatchEvent.patch new file mode 100644 index 0000000000..60a1317040 --- /dev/null +++ b/patches/server-unmapped/0001/0408-Add-ThrownEggHatchEvent.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 9 Feb 2020 00:19:05 -0600 +Subject: [PATCH] Add ThrownEggHatchEvent + +Adds a new event similar to PlayerEggThrowEvent, but without the Player requirement +(dispensers can throw eggs to hatch them, too). + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityEgg.java b/src/main/java/net/minecraft/world/entity/projectile/EntityEgg.java +index 01cee6599f5d21a29310c30a8b1e505023d1a260..dc2e51718395494f60b0376d65d496daf2f76e71 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityEgg.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityEgg.java +@@ -67,6 +67,16 @@ public class EntityEgg extends EntityProjectileThrowable { + hatchingType = event.getHatchingType(); + } + ++ // Paper start ++ com.destroystokyo.paper.event.entity.ThrownEggHatchEvent event = new com.destroystokyo.paper.event.entity.ThrownEggHatchEvent((org.bukkit.entity.Egg) getBukkitEntity(), hatching, b0, hatchingType); ++ event.callEvent(); ++ ++ b0 = event.getNumHatches(); ++ hatching = event.isHatching(); ++ hatchingType = event.getHatchingType(); ++ // Paper end ++ ++ + if (hatching) { + for (int i = 0; i < b0; ++i) { + Entity entity = world.getWorld().createEntity(new org.bukkit.Location(world.getWorld(), this.locX(), this.locY(), this.locZ(), this.yaw, 0.0F), hatchingType.getEntityClass()); diff --git a/patches/server-unmapped/0001/0409-Optimise-random-block-ticking.patch b/patches/server-unmapped/0001/0409-Optimise-random-block-ticking.patch new file mode 100644 index 0000000000..340a9f9e53 --- /dev/null +++ b/patches/server-unmapped/0001/0409-Optimise-random-block-ticking.patch @@ -0,0 +1,407 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 27 Jan 2020 21:28:00 -0800 +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/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3edc8e52e06a62ce9f8cc734fd7458b37cfaad91 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java +@@ -0,0 +1,46 @@ ++package com.destroystokyo.paper.util.math; ++ ++import java.util.Random; ++ ++public final class ThreadUnsafeRandom extends Random { ++ ++ // 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; ++ ++ @Override ++ public void setSeed(long seed) { ++ // note: called by Random constructor ++ this.seed = initialScramble(seed); ++ } ++ ++ @Override ++ protected 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/core/BlockPosition.java b/src/main/java/net/minecraft/core/BlockPosition.java +index 4c9ec211470f95d538d1d95c74796190edf99b87..8c0aeb51f5e230fd6109e750732eb54559bc9637 100644 +--- a/src/main/java/net/minecraft/core/BlockPosition.java ++++ b/src/main/java/net/minecraft/core/BlockPosition.java +@@ -468,6 +468,7 @@ public class BlockPosition extends BaseBlockPosition { + return this.d(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2)); + } + ++ public final BlockPosition.MutableBlockPosition setValues(final BaseBlockPosition baseblockposition) { return this.g(baseblockposition); } // Paper - OBFHELPER + public BlockPosition.MutableBlockPosition g(BaseBlockPosition baseblockposition) { + return this.d(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); + } +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 18169d48598b873b0d7507bb55a1a0bad0ab6566..291c8091dbe58bc8e2c9ed8f3c80428386a2a1d1 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -675,7 +675,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { + }); + } + +- public void a(Chunk chunk, int i) { ++ // Paper start - optimise random block ticking ++ private final BlockPosition.MutableBlockPosition chunkTickMutablePosition = new BlockPosition.MutableBlockPosition(); ++ private final com.destroystokyo.paper.util.math.ThreadUnsafeRandom randomTickRandom = new com.destroystokyo.paper.util.math.ThreadUnsafeRandom(); ++ // Paper end ++ ++ public void a(Chunk chunk, int i) { final int randomTickSpeed = i; // Paper + ChunkCoordIntPair chunkcoordintpair = chunk.getPos(); + boolean flag = this.isRaining(); + int j = chunkcoordintpair.d(); +@@ -683,10 +688,10 @@ public class WorldServer extends World implements GeneratorAccessSeed { + GameProfilerFiller gameprofilerfiller = this.getMethodProfiler(); + + gameprofilerfiller.enter("thunder"); +- BlockPosition blockposition; ++ final BlockPosition.MutableBlockPosition blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change + + if (!this.paperConfig.disableThunder && flag && this.W() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder +- blockposition = this.a(this.a(j, 0, k, 15)); ++ blockposition.setValues(this.a(this.a(j, 0, k, 15))); // Paper + if (this.isRainingAt(blockposition)) { + DifficultyDamageScaler difficultydamagescaler = this.getDamageScaler(blockposition); + boolean flag1 = this.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.b() * paperConfig.skeleHorseSpawnChance; // Paper +@@ -709,59 +714,77 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + gameprofilerfiller.exitEnter("iceandsnow"); +- if (!this.paperConfig.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow +- blockposition = this.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, this.a(j, 0, k, 15)); +- BlockPosition blockposition1 = blockposition.down(); ++ if (!this.paperConfig.disableIceAndSnow && this.randomTickRandom.nextInt(16) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking ++ // Paper start - optimise chunk ticking ++ this.getRandomBlockPosition(j, 0, k, 15, blockposition); ++ int normalY = chunk.getHighestBlockY(HeightMap.Type.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15); ++ int downY = normalY - 1; ++ blockposition.setY(normalY); ++ // Paper end + BiomeBase biomebase = this.getBiome(blockposition); + +- if (biomebase.a(this, blockposition1)) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.getBlockData(), null); // CraftBukkit ++ // Paper start - optimise chunk ticking ++ blockposition.setY(downY); ++ if (biomebase.a(this, blockposition)) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.ICE.getBlockData(), null); // CraftBukkit ++ // Paper end + } + ++ blockposition.setY(normalY); // Paper + if (flag && biomebase.b(this, blockposition)) { + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.getBlockData(), null); // CraftBukkit + } + +- if (flag && this.getBiome(blockposition1).c() == BiomeBase.Precipitation.RAIN) { +- this.getType(blockposition1).getBlock().c((World) this, blockposition1); ++ // Paper start - optimise chunk ticking ++ blockposition.setY(downY); ++ if (flag && this.getBiome(blockposition).c() == BiomeBase.Precipitation.RAIN) { ++ chunk.getType(blockposition).getBlock().c((World) this, blockposition); ++ // Paper end + } + } + +- gameprofilerfiller.exitEnter("tickBlocks"); +- timings.chunkTicksBlocks.startTiming(); // Paper ++ // Paper start - optimise random block ticking ++ gameprofilerfiller.exit(); + if (i > 0) { +- ChunkSection[] achunksection = chunk.getSections(); +- int l = achunksection.length; ++ gameprofilerfiller.enter("randomTick"); ++ timings.chunkTicksBlocks.startTiming(); // Paper + +- for (int i1 = 0; i1 < l; ++i1) { +- ChunkSection chunksection = achunksection[i1]; ++ ChunkSection[] sections = chunk.getSections(); + +- if (chunksection != Chunk.a && chunksection.d()) { +- int j1 = chunksection.getYPosition(); ++ for (int sectionIndex = 0; sectionIndex < 16; ++sectionIndex) { ++ ChunkSection section = sections[sectionIndex]; ++ if (section == null || section.tickingList.size() == 0) { ++ continue; ++ } + +- for (int k1 = 0; k1 < i; ++k1) { +- BlockPosition blockposition2 = this.a(j, j1, k, 15); ++ int yPos = sectionIndex << 4; + +- gameprofilerfiller.enter("randomTick"); +- IBlockData iblockdata = chunksection.getType(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k); ++ for (int a = 0; a < randomTickSpeed; ++a) { ++ int tickingBlocks = section.tickingList.size(); ++ int index = this.randomTickRandom.nextInt(16 * 16 * 16); ++ if (index >= tickingBlocks) { ++ continue; ++ } + +- if (iblockdata.isTicking()) { +- iblockdata.b(this, blockposition2, this.random); +- } ++ 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; + +- Fluid fluid = iblockdata.getFluid(); ++ BlockPosition blockposition2 = blockposition.setValues(j + randomX, randomY, k + randomZ); ++ IBlockData iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); + +- if (fluid.f()) { +- fluid.b(this, blockposition2, this.random); +- } ++ iblockdata.b(this, blockposition2, this.randomTickRandom); + +- gameprofilerfiller.exit(); +- } ++ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method. ++ // TODO CHECK ON UPDATE + } + } ++ gameprofilerfiller.exit(); ++ timings.chunkTicksBlocks.stopTiming(); // Paper ++ // Paper end + } +- timings.chunkTicksBlocks.stopTiming(); // Paper +- gameprofilerfiller.exit(); + } + + protected BlockPosition a(BlockPosition blockposition) { +diff --git a/src/main/java/net/minecraft/util/DataBits.java b/src/main/java/net/minecraft/util/DataBits.java +index c4f3b680512fb15cea01ad12d0a00c6e60bf34b7..cfa444cf384920d446c6dc14b23e5158fc28df3b 100644 +--- a/src/main/java/net/minecraft/util/DataBits.java ++++ b/src/main/java/net/minecraft/util/DataBits.java +@@ -112,4 +112,32 @@ public class DataBits { + } + + } ++ ++ // Paper start ++ public final void forEach(DataBitConsumer consumer) { ++ int i = 0; ++ long[] along = this.b; ++ int j = along.length; ++ ++ for (int k = 0; k < j; ++k) { ++ long l = along[k]; ++ ++ for (int i1 = 0; i1 < this.f; ++i1) { ++ consumer.accept(i, (int) (l & this.d)); ++ l >>= this.c; ++ ++i; ++ if (i >= this.e) { ++ return; ++ } ++ } ++ } ++ } ++ ++ @FunctionalInterface ++ public static interface DataBitConsumer { ++ ++ void accept(int location, int data); ++ ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java b/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java +index 09a6310af6712d36c20167256b60dc3235e76021..ecec8a3c4d4b5d491f79ad60d7ce5a118f30b3db 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityTurtle.java +@@ -91,7 +91,7 @@ public class EntityTurtle extends EntityAnimal { + } + + public void setHomePos(BlockPosition blockposition) { +- this.datawatcher.set(EntityTurtle.bp, blockposition); ++ this.datawatcher.set(EntityTurtle.bp, blockposition.immutableCopy()); // Paper - called with mutablepos... + } + + public BlockPosition getHomePos() { // Paper - public +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index c4680142bf23d30169555abe7db78d85811e042b..cc41dcd85760b57bb8076b37e9a907d1cb4e12c7 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -1472,10 +1472,18 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public abstract ITagRegistry p(); + + public BlockPosition a(int i, int j, int k, int l) { ++ // Paper start - allow use of mutable pos ++ BlockPosition.MutableBlockPosition ret = new BlockPosition.MutableBlockPosition(); ++ this.getRandomBlockPosition(i, j, k, l, ret); ++ return ret.immutableCopy(); ++ } ++ public final BlockPosition.MutableBlockPosition getRandomBlockPosition(int i, int j, int k, int l, BlockPosition.MutableBlockPosition out) { ++ // Paper end + this.n = this.n * 3 + 1013904223; + int i1 = this.n >> 2; + +- return new BlockPosition(i + (i1 & 15), j + (i1 >> 16 & l), k + (i1 >> 8 & 15)); ++ out.setValues(i + (i1 & 15), j + (i1 >> 16 & l), k + (i1 >> 8 & 15)); // Paper - change to setValues call ++ return out; // Paper + } + + public boolean isSavingDisabled() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index e7bb33125a25b9e5a68013b15d7b5b6b6769ab9b..fc55e89260fdec2c5045e8f00e091191980ff1f2 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -640,8 +640,8 @@ public class Chunk implements IChunkAccess { + this.entities.remove(entity); // Paper + } + +- @Override +- public int getHighestBlock(HeightMap.Type heightmap_type, int i, int j) { ++ public final int getHighestBlockY(HeightMap.Type heightmap_type, int i, int j) { return this.getHighestBlock(heightmap_type, i, j) + 1; } // Paper - sort of an obfhelper, but without -1 ++ @Override public int getHighestBlock(HeightMap.Type heightmap_type, int i, int j) { // Paper + return ((HeightMap) this.heightMap.get(heightmap_type)).a(i & 15, j & 15) - 1; + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +index 0b4e346daaea91565fde2f789fafa8b431a7b042..4bc26a7a4ae91aac90c256758ec8868d83027c0c 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +@@ -14,12 +14,14 @@ import net.minecraft.world.level.material.Fluid; + public class ChunkSection { + + public static final DataPalette GLOBAL_PALETTE = new DataPaletteGlobal<>(Block.REGISTRY_ID, Blocks.AIR.getBlockData()); +- private final int yPos; ++ final int yPos; // Paper - private -> package-private + short nonEmptyBlockCount; // Paper - package-private +- private short tickingBlockCount; ++ short tickingBlockCount; // Paper - private -> package-private + private short e; + final DataPaletteBlock blockIds; // Paper - package-private + ++ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper ++ + // Paper start - Anti-Xray - Add parameters + @Deprecated public ChunkSection(int i) { this(i, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere + public ChunkSection(int i, IChunkAccess chunk, World world, boolean initializeBlocks) { +@@ -74,6 +76,9 @@ public class ChunkSection { + --this.nonEmptyBlockCount; + if (iblockdata1.isTicking()) { + --this.tickingBlockCount; ++ // Paper start ++ this.tickingList.remove(i, j, k); ++ // Paper end + } + } + +@@ -85,6 +90,9 @@ public class ChunkSection { + ++this.nonEmptyBlockCount; + if (iblockdata.isTicking()) { + ++this.tickingBlockCount; ++ // Paper start ++ this.tickingList.add(i, j, k, iblockdata); ++ // Paper end + } + } + +@@ -120,23 +128,29 @@ public class ChunkSection { + } + + public void recalcBlockCounts() { ++ // Paper start ++ this.tickingList.clear(); ++ // Paper end + this.nonEmptyBlockCount = 0; + this.tickingBlockCount = 0; + this.e = 0; +- this.blockIds.a((iblockdata, i) -> { ++ this.blockIds.forEachLocation((iblockdata, location) -> { // Paper + Fluid fluid = iblockdata.getFluid(); + + if (!iblockdata.isAir()) { +- this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i); ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); + if (iblockdata.isTicking()) { +- this.tickingBlockCount = (short) (this.tickingBlockCount + i); ++ this.tickingBlockCount = (short) (this.tickingBlockCount + 1); ++ // Paper start ++ this.tickingList.add(location, iblockdata); ++ // Paper end + } + } + + if (!fluid.isEmpty()) { +- this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i); ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); + if (fluid.f()) { +- this.e = (short) (this.e + i); ++ this.e = (short) (this.e + 1); + } + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +index 68d53a51acc9790b9cda20ec4d2ec6edd1baac1a..86dfab740883c138a0df8a3da9dfb4eb9acefaa3 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java ++++ b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +@@ -286,6 +286,14 @@ public class DataPaletteBlock implements DataPaletteExpandable { + }); + } + ++ // Paper start ++ public void forEachLocation(DataPaletteBlock.a datapaletteblock_a) { ++ this.getDataBits().forEach((int location, int data) -> { ++ datapaletteblock_a.accept(this.getDataPalette().getObject(data), location); ++ }); ++ } ++ // Paper end ++ + @FunctionalInterface + public interface a { + diff --git a/patches/server-unmapped/0001/0410-Entity-Jump-API.patch b/patches/server-unmapped/0001/0410-Entity-Jump-API.patch new file mode 100644 index 0000000000..17e2ec967b --- /dev/null +++ b/patches/server-unmapped/0001/0410-Entity-Jump-API.patch @@ -0,0 +1,60 @@ +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 + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 02d3b792cc9769b5daa6fcac57f5cda320a2a29e..86c6a8fd4511dfe426cc1651d289f38b467d3029 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -2876,8 +2876,10 @@ public abstract class EntityLiving extends Entity { + } else if (this.aQ() && (!this.onGround || d7 > d8)) { + this.c((Tag) TagsFluid.LAVA); + } else if ((this.onGround || flag && d7 <= d8) && this.jumpTicks == 0) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper + this.jump(); + this.jumpTicks = 10; ++ } else { this.setJumping(false); } // Paper - setJumping(false) stops a potential loop + } + } else { + this.jumpTicks = 0; +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityPanda.java b/src/main/java/net/minecraft/world/entity/animal/EntityPanda.java +index f755607872920caae1410d38c431c16b5238c00f..711b322007a0973ff0aebf3c25efbae8fc7741d0 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityPanda.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityPanda.java +@@ -490,7 +490,9 @@ public class EntityPanda extends EntityAnimal { + EntityPanda entitypanda = (EntityPanda) iterator.next(); + + if (!entitypanda.isBaby() && entitypanda.onGround && !entitypanda.isInWater() && entitypanda.fh()) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper + entitypanda.jump(); ++ } else { this.setJumping(false); } // Paper - 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 ff586b8366a6298f1906551b068e8abb26fcabc7..b18292ef2e00b4ef8a0b2da5f63a596dbd04b1fd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -799,5 +799,20 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public boolean isHandRaised() { + return getHandle().isHandRaised(); + } ++ ++ @Override ++ public boolean isJumping() { ++ return getHandle().jumping; ++ } ++ ++ @Override ++ public void setJumping(boolean jumping) { ++ getHandle().setJumping(jumping); ++ if (jumping && getHandle() instanceof EntityInsentient) { ++ // this is needed to actually make a mob jump ++ ((EntityInsentient) getHandle()).getControllerJump().jump(); ++ } ++ } ++ + // Paper end + } diff --git a/patches/server-unmapped/0001/0411-Add-option-to-nerf-pigmen-from-nether-portals.patch b/patches/server-unmapped/0001/0411-Add-option-to-nerf-pigmen-from-nether-portals.patch new file mode 100644 index 0000000000..1a54ef16f6 --- /dev/null +++ b/patches/server-unmapped/0001/0411-Add-option-to-nerf-pigmen-from-nether-portals.patch @@ -0,0 +1,71 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 7fbd501d70dccf869a4454e2789a5d68f2e15754..9e4591ddc4b755f4ff5a6f1078b51cb13db80480 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -594,4 +594,9 @@ public class PaperWorldConfig { + disableHopperMoveEvents = getBoolean("hopper.disable-move-event", disableHopperMoveEvents); + log("Hopper Move Item Events: " + (disableHopperMoveEvents ? "disabled" : "enabled")); + } ++ ++ public boolean nerfNetherPortalPigmen = false; ++ private void nerfNetherPortalPigmen() { ++ nerfNetherPortalPigmen = getBoolean("game-mechanics.nerf-pigmen-from-nether-portals", nerfNetherPortalPigmen); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index d676eaad8179cdeae410038e58ddafe0fe541ccc..68fdb01c3f11c3b060d3d621099d67f6b29431d6 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -278,6 +278,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + public long activatedTick = Integer.MIN_VALUE; + public boolean isTemporarilyActive = false; // Paper + public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one ++ public boolean fromNetherPortal; // Paper + protected int numCollisions = 0; // Paper + public void inactiveTick() { } + // Spigot end +@@ -1693,6 +1694,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + if (spawnedViaMobSpawner) { + nbttagcompound.setBoolean("Paper.FromMobSpawner", true); + } ++ if (fromNetherPortal) { ++ nbttagcompound.setBoolean("Paper.FromNetherPortal", true); ++ } + // Paper end + return nbttagcompound; + } catch (Throwable throwable) { +@@ -1823,6 +1827,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + spawnedViaMobSpawner = nbttagcompound.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status ++ fromNetherPortal = nbttagcompound.getBoolean("Paper.FromNetherPortal"); + if (nbttagcompound.hasKey("Paper.SpawnReason")) { + String spawnReasonName = nbttagcompound.getString("Paper.SpawnReason"); + try { +diff --git a/src/main/java/net/minecraft/world/level/block/BlockPortal.java b/src/main/java/net/minecraft/world/level/block/BlockPortal.java +index e115ff86987c69f5e3571af5d7f034f24a3f6bba..5f797260eff317409a5039b88b01ad79ee2fdd91 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockPortal.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockPortal.java +@@ -7,6 +7,7 @@ import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.server.level.WorldServer; + import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityInsentient; + import net.minecraft.world.entity.EntityTypes; + import net.minecraft.world.entity.EnumMobSpawn; + import net.minecraft.world.entity.player.EntityHuman; +@@ -62,6 +63,8 @@ public class BlockPortal extends Block { + + if (entity != null) { + entity.resetPortalCooldown(); ++ entity.fromNetherPortal = true; // Paper ++ if (worldserver.paperConfig.nerfNetherPortalPigmen) ((EntityInsentient) entity).aware = false; // Paper + } + } + } diff --git a/patches/server-unmapped/0001/0412-Make-the-GUI-graph-fancier.patch b/patches/server-unmapped/0001/0412-Make-the-GUI-graph-fancier.patch new file mode 100644 index 0000000000..7c19039ff5 --- /dev/null +++ b/patches/server-unmapped/0001/0412-Make-the-GUI-graph-fancier.patch @@ -0,0 +1,436 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 2 Feb 2020 04:00:40 -0600 +Subject: [PATCH] Make the GUI graph fancier + + +diff --git a/src/main/java/com/destroystokyo/paper/gui/GraphColor.java b/src/main/java/com/destroystokyo/paper/gui/GraphColor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a4e641fdcccd3efcd1a2865dc6dc28d50671b995 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/gui/GraphColor.java +@@ -0,0 +1,44 @@ ++package com.destroystokyo.paper.gui; ++ ++import java.awt.Color; ++ ++public class GraphColor { ++ private static final Color[] colorLine = new Color[101]; ++ private static final Color[] colorFill = new Color[101]; ++ ++ static { ++ for (int i = 0; i < 101; i++) { ++ Color color = createColor(i); ++ colorLine[i] = new Color(color.getRed() / 2, color.getGreen() / 2, color.getBlue() / 2, 255); ++ colorFill[i] = new Color(colorLine[i].getRed(), colorLine[i].getGreen(), colorLine[i].getBlue(), 125); ++ } ++ } ++ ++ public static Color getLineColor(int percent) { ++ return colorLine[percent]; ++ } ++ ++ public static Color getFillColor(int percent) { ++ return colorFill[percent]; ++ } ++ ++ private static Color createColor(int percent) { ++ if (percent <= 50) { ++ return new Color(0X00FF00); ++ } ++ ++ int value = 510 - (int) (Math.min(Math.max(0, ((percent - 50) / 50F)), 1) * 510); ++ ++ int red, green; ++ if (value < 255) { ++ red = 255; ++ green = (int) (Math.sqrt(value) * 16); ++ } else { ++ green = 255; ++ value = value - 255; ++ red = 255 - (value * value / 255); ++ } ++ ++ return new Color(red, green, 0); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/gui/GraphData.java b/src/main/java/com/destroystokyo/paper/gui/GraphData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..186fc722965e403f76b1480e1c2381fc34e29049 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/gui/GraphData.java +@@ -0,0 +1,47 @@ ++package com.destroystokyo.paper.gui; ++ ++import java.awt.Color; ++ ++public class GraphData { ++ private long total; ++ private long free; ++ private long max; ++ private long usedMem; ++ private int usedPercent; ++ ++ public GraphData(long total, long free, long max) { ++ this.total = total; ++ this.free = free; ++ this.max = max; ++ this.usedMem = total - free; ++ this.usedPercent = usedMem == 0 ? 0 : (int) (usedMem * 100L / max); ++ } ++ ++ public long getTotal() { ++ return total; ++ } ++ ++ public long getFree() { ++ return free; ++ } ++ ++ public long getMax() { ++ return max; ++ } ++ ++ public long getUsedMem() { ++ return usedMem; ++ } ++ ++ public int getUsedPercent() { ++ return usedPercent; ++ } ++ ++ public Color getFillColor() { ++ return GraphColor.getFillColor(usedPercent); ++ } ++ ++ public Color getLineColor() { ++ return GraphColor.getLineColor(usedPercent); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java b/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0f29ad583e798c09b2fe3f568ed50cbc719e40e2 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java +@@ -0,0 +1,41 @@ ++package com.destroystokyo.paper.gui; ++ ++import net.minecraft.server.MinecraftServer; ++ ++import javax.swing.JPanel; ++import javax.swing.Timer; ++import java.awt.BorderLayout; ++import java.awt.Dimension; ++ ++public class GuiStatsComponent extends JPanel { ++ private final Timer timer; ++ private final RAMGraph ramGraph; ++ ++ public GuiStatsComponent(MinecraftServer server) { ++ super(new BorderLayout()); ++ ++ setOpaque(false); ++ ++ ramGraph = new RAMGraph(); ++ RAMDetails ramDetails = new RAMDetails(server); ++ ++ add(ramGraph, "North"); ++ add(ramDetails, "Center"); ++ ++ timer = new Timer(500, (event) -> { ++ ramGraph.update(); ++ ramDetails.update(); ++ }); ++ timer.start(); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 200); ++ } ++ ++ public void stop() { a(); } public void a() { ++ timer.stop(); ++ ramGraph.stop(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +new file mode 100644 +index 0000000000000000000000000000000000000000..67d064e3959ed8d886df30ce9d97f86c2443fa39 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +@@ -0,0 +1,73 @@ ++package com.destroystokyo.paper.gui; ++ ++import net.minecraft.SystemUtils; ++import net.minecraft.server.MinecraftServer; ++ ++import javax.swing.DefaultListCellRenderer; ++import javax.swing.DefaultListSelectionModel; ++import javax.swing.JList; ++import javax.swing.border.EmptyBorder; ++import java.awt.Dimension; ++import java.text.DecimalFormat; ++import java.text.DecimalFormatSymbols; ++import java.util.Locale; ++import java.util.Vector; ++ ++public class RAMDetails extends JList { ++ public static final DecimalFormat DECIMAL_FORMAT = SystemUtils.peek(new DecimalFormat("########0.000"), (format) ++ -> format.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT))); ++ ++ private final MinecraftServer server; ++ ++ public RAMDetails(MinecraftServer server) { ++ this.server = server; ++ ++ setBorder(new EmptyBorder(0, 10, 0, 0)); ++ setFixedCellHeight(20); ++ setOpaque(false); ++ ++ DefaultListCellRenderer renderer = new DefaultListCellRenderer(); ++ renderer.setOpaque(false); ++ setCellRenderer(renderer); ++ ++ setSelectionModel(new DefaultListSelectionModel() { ++ @Override ++ public void setAnchorSelectionIndex(final int anchorIndex) { ++ } ++ ++ @Override ++ public void setLeadAnchorNotificationEnabled(final boolean flag) { ++ } ++ ++ @Override ++ public void setLeadSelectionIndex(final int leadIndex) { ++ } ++ ++ @Override ++ public void setSelectionInterval(final int index0, final int index1) { ++ } ++ }); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 100); ++ } ++ ++ public void update() { ++ GraphData data = RAMGraph.DATA.peekLast(); ++ Vector vector = new Vector<>(); ++ vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)"); ++ vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb"); ++ vector.add("Avg tick: " + DECIMAL_FORMAT.format(getAverage(server.getTickTimes())) + " ms"); ++ setListData(vector); ++ } ++ ++ public double getAverage(long[] tickTimes) { ++ long total = 0L; ++ for (long value : tickTimes) { ++ total += value; ++ } ++ return ((double) total / (double) tickTimes.length) * 1.0E-6D; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java b/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c3e54da4ab6440811aab2f9dd1e218802ac13285 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java +@@ -0,0 +1,144 @@ ++package com.destroystokyo.paper.gui; ++ ++import javax.swing.JComponent; ++import javax.swing.SwingUtilities; ++import javax.swing.Timer; ++import javax.swing.ToolTipManager; ++import java.awt.Color; ++import java.awt.Dimension; ++import java.awt.Graphics; ++import java.awt.MouseInfo; ++import java.awt.Point; ++import java.awt.PointerInfo; ++import java.awt.event.MouseAdapter; ++import java.awt.event.MouseEvent; ++import java.text.SimpleDateFormat; ++import java.util.Date; ++import java.util.LinkedList; ++import java.util.concurrent.TimeUnit; ++ ++public class RAMGraph extends JComponent { ++ public static final LinkedList DATA = new LinkedList() { ++ @Override ++ public boolean add(GraphData data) { ++ if (size() >= 348) { ++ remove(); ++ } ++ return super.add(data); ++ } ++ }; ++ ++ static { ++ GraphData empty = new GraphData(0, 0, 0); ++ for (int i = 0; i < 350; i++) { ++ DATA.add(empty); ++ } ++ } ++ ++ private final Timer timer; ++ private final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); ++ ++ private int currentTick; ++ ++ public RAMGraph() { ++ ToolTipManager.sharedInstance().setInitialDelay(0); ++ ++ addMouseListener(new MouseAdapter() { ++ final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay(); ++ final int dismissDelayMinutes = (int) TimeUnit.MINUTES.toMillis(10); ++ ++ @Override ++ public void mouseEntered(MouseEvent me) { ++ ToolTipManager.sharedInstance().setDismissDelay(dismissDelayMinutes); ++ } ++ ++ @Override ++ public void mouseExited(MouseEvent me) { ++ ToolTipManager.sharedInstance().setDismissDelay(defaultDismissTimeout); ++ } ++ }); ++ ++ timer = new Timer(50, (event) -> repaint()); ++ timer.start(); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 110); ++ } ++ ++ public void update() { ++ Runtime jvm = Runtime.getRuntime(); ++ DATA.add(new GraphData(jvm.totalMemory(), jvm.freeMemory(), jvm.maxMemory())); ++ ++ PointerInfo pointerInfo = MouseInfo.getPointerInfo(); ++ if (pointerInfo != null) { ++ Point point = pointerInfo.getLocation(); ++ if (point != null) { ++ Point loc = new Point(point); ++ SwingUtilities.convertPointFromScreen(loc, this); ++ if (this.contains(loc)) { ++ ToolTipManager.sharedInstance().mouseMoved( ++ new MouseEvent(this, -1, System.currentTimeMillis(), 0, loc.x, loc.y, ++ point.x, point.y, 0, false, 0)); ++ } ++ } ++ } ++ ++ currentTick++; ++ } ++ ++ @Override ++ public void paint(Graphics graphics) { ++ graphics.setColor(new Color(0xFFFFFFFF)); ++ graphics.fillRect(0, 0, 350, 100); ++ ++ graphics.setColor(new Color(0x888888)); ++ graphics.drawLine(1, 25, 348, 25); ++ graphics.drawLine(1, 50, 348, 50); ++ graphics.drawLine(1, 75, 348, 75); ++ ++ int i = 0; ++ for (GraphData data : DATA) { ++ i++; ++ if ((i + currentTick) % 120 == 0) { ++ graphics.setColor(new Color(0x888888)); ++ graphics.drawLine(i, 1, i, 99); ++ } ++ int used = data.getUsedPercent(); ++ if (used > 0) { ++ Color color = data.getLineColor(); ++ graphics.setColor(data.getFillColor()); ++ graphics.fillRect(i, 100 - used, 1, used); ++ graphics.setColor(color); ++ graphics.fillRect(i, 100 - used, 1, 1); ++ } ++ } ++ ++ graphics.setColor(new Color(0xFF000000)); ++ graphics.drawRect(0, 0, 348, 100); ++ ++ Point m = getMousePosition(); ++ if (m != null && m.x > 0 && m.x < 348 && m.y > 0 && m.y < 100) { ++ GraphData data = DATA.get(m.x); ++ int used = data.getUsedPercent(); ++ graphics.setColor(new Color(0x000000)); ++ graphics.drawLine(m.x, 1, m.x, 99); ++ graphics.drawOval(m.x - 2, 100 - used - 2, 5, 5); ++ graphics.setColor(data.getLineColor()); ++ graphics.fillOval(m.x - 2, 100 - used - 2, 5, 5); ++ setToolTipText(String.format("Used: %s mb (%s%%)
    %s", ++ Math.round(data.getUsedMem() / 1024F / 1024F), ++ used, getTime(m.x))); ++ } ++ } ++ ++ public String getTime(int halfSeconds) { ++ int millis = (348 - halfSeconds) / 2 * 1000; ++ return TIME_FORMAT.format(new Date((System.currentTimeMillis() - millis))); ++ } ++ ++ public void stop() { ++ timer.stop(); ++ } ++} +diff --git a/src/main/java/net/minecraft/SystemUtils.java b/src/main/java/net/minecraft/SystemUtils.java +index 46d82c1548088b8305f758699388edf0d5d4d050..397194b3e90c9df39cfae17b401c7ac891b0dbb7 100644 +--- a/src/main/java/net/minecraft/SystemUtils.java ++++ b/src/main/java/net/minecraft/SystemUtils.java +@@ -260,6 +260,7 @@ public class SystemUtils { + return supplier.get(); + } + ++ public static T peek(T t0, Consumer consumer) { return a(t0, consumer); } // Paper - OBFHELPER + public static T a(T t0, Consumer consumer) { + consumer.accept(t0); + return t0; +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index af7bef41d341218f25943eb1e10831cc0be6840a..dc817d7c7187de2b37485bef126fb0765a5caf63 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -217,7 +217,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { ++ private static final DecimalFormat a = (DecimalFormat) SystemUtils.a(new DecimalFormat("########0.000"), (decimalformat) -> { // Paper - decompile error + decimalformat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)); + }); + private final int[] b = new int[256]; +diff --git a/src/main/java/net/minecraft/server/gui/ServerGUI.java b/src/main/java/net/minecraft/server/gui/ServerGUI.java +index dc2a36f6dbc83ba25bcd59d981f75499adbd22e5..c2c075b9e3b70f863b6c450e4f31d6fde2935be6 100644 +--- a/src/main/java/net/minecraft/server/gui/ServerGUI.java ++++ b/src/main/java/net/minecraft/server/gui/ServerGUI.java +@@ -91,7 +91,7 @@ public class ServerGUI extends JComponent { + + private JComponent c() { + JPanel jpanel = new JPanel(new BorderLayout()); +- GuiStatsComponent guistatscomponent = new GuiStatsComponent(this.c); ++ com.destroystokyo.paper.gui.GuiStatsComponent guistatscomponent = new com.destroystokyo.paper.gui.GuiStatsComponent(this.c); // Paper + + this.e.add(guistatscomponent::a); + jpanel.add(guistatscomponent, "North"); diff --git a/patches/server-unmapped/0001/0413-add-hand-to-BlockMultiPlaceEvent.patch b/patches/server-unmapped/0001/0413-add-hand-to-BlockMultiPlaceEvent.patch new file mode 100644 index 0000000000..6ea29810d2 --- /dev/null +++ b/patches/server-unmapped/0001/0413-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 8ad6540fc4af95409d1c9169e3b0ed78310eeac5..b5a46134f2aef3e44fca13cd46f1e52d409a58b5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -331,13 +331,18 @@ public class CraftEventFactory { + } + + org.bukkit.inventory.ItemStack item; ++ //Paper start - add hand to BlockMultiPlaceEvent ++ EquipmentSlot equipmentSlot; + if (hand == EnumHand.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-unmapped/0001/0414-Prevent-teleporting-dead-entities.patch b/patches/server-unmapped/0001/0414-Prevent-teleporting-dead-entities.patch new file mode 100644 index 0000000000..29dc1ca8f1 --- /dev/null +++ b/patches/server-unmapped/0001/0414-Prevent-teleporting-dead-entities.patch @@ -0,0 +1,21 @@ +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/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index cfdfa3ea95a525af25c7aa830f8e31d5afe56d65..f02ddd53df4674a2b5e0bb142db756d1f153d69b 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1469,6 +1469,10 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + + private void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { ++ if (player.dead) { ++ LOGGER.info("Attempt to teleport dead player {} restricted", player.getName()); ++ return; ++ } + // CraftBukkit start + if (Float.isNaN(f)) { + f = 0; diff --git a/patches/server-unmapped/0001/0415-Validate-tripwire-hook-placement-before-update.patch b/patches/server-unmapped/0001/0415-Validate-tripwire-hook-placement-before-update.patch new file mode 100644 index 0000000000..75c8efe983 --- /dev/null +++ b/patches/server-unmapped/0001/0415-Validate-tripwire-hook-placement-before-update.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 7 Mar 2020 00:07:51 +0000 +Subject: [PATCH] Validate tripwire hook placement before update + + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockTripwireHook.java b/src/main/java/net/minecraft/world/level/block/BlockTripwireHook.java +index 8b24978cb54f2102d61f27038dedc5e3dc392dbc..5e3cf96b813d2871adf7a7f870af6c6a5dd878c1 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockTripwireHook.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockTripwireHook.java +@@ -170,6 +170,7 @@ public class BlockTripwireHook extends Block { + + this.a(world, blockposition, flag4, flag5, flag2, flag3); + if (!flag) { ++ if (world.getType(blockposition).getBlock() == Blocks.TRIPWIRE_HOOK) // Paper - validate + world.setTypeAndData(blockposition, (IBlockData) iblockdata3.set(BlockTripwireHook.FACING, enumdirection), 3); + if (flag1) { + this.a(world, blockposition, enumdirection); diff --git a/patches/server-unmapped/0001/0416-Add-option-to-allow-iron-golems-to-spawn-in-air.patch b/patches/server-unmapped/0001/0416-Add-option-to-allow-iron-golems-to-spawn-in-air.patch new file mode 100644 index 0000000000..bc4517ce5b --- /dev/null +++ b/patches/server-unmapped/0001/0416-Add-option-to-allow-iron-golems-to-spawn-in-air.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 13 Apr 2019 16:50:58 -0500 +Subject: [PATCH] Add option to allow iron golems to spawn in air + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 9e4591ddc4b755f4ff5a6f1078b51cb13db80480..fe1c9dd8258ec8c3fdf343d4a44de2be2ae3d35f 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -387,6 +387,11 @@ public class PaperWorldConfig { + scanForLegacyEnderDragon = getBoolean("game-mechanics.scan-for-legacy-ender-dragon", true); + } + ++ public boolean ironGolemsCanSpawnInAir = false; ++ private void ironGolemsCanSpawnInAir() { ++ ironGolemsCanSpawnInAir = getBoolean("iron-golems-can-spawn-in-air", ironGolemsCanSpawnInAir); ++ } ++ + public boolean armorStandEntityLookups = true; + private void armorStandEntityLookups() { + armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", true); +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityIronGolem.java b/src/main/java/net/minecraft/world/entity/animal/EntityIronGolem.java +index 49495cfbcf9b7742583536b87fc7cbd7c7c4c867..5e2b49d120b724cb5a7ae00940ded4f4875ea8a1 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityIronGolem.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityIronGolem.java +@@ -297,7 +297,7 @@ public class EntityIronGolem extends EntityGolem implements IEntityAngerable { + BlockPosition blockposition1 = blockposition.down(); + IBlockData iblockdata = iworldreader.getType(blockposition1); + +- if (!iblockdata.a((IBlockAccess) iworldreader, blockposition1, (Entity) this)) { ++ if (!iblockdata.a((IBlockAccess) iworldreader, blockposition1, (Entity) this) && !world.paperConfig.ironGolemsCanSpawnInAir) { // Paper + return false; + } else { + for (int i = 1; i < 3; ++i) { diff --git a/patches/server-unmapped/0001/0417-Configurable-chance-of-villager-zombie-infection.patch b/patches/server-unmapped/0001/0417-Configurable-chance-of-villager-zombie-infection.patch new file mode 100644 index 0000000000..0a2c05d50a --- /dev/null +++ b/patches/server-unmapped/0001/0417-Configurable-chance-of-villager-zombie-infection.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zero +Date: Sat, 22 Feb 2020 16:10:31 -0500 +Subject: [PATCH] Configurable chance of villager zombie infection + +This allows you to solve an issue in vanilla behavior where: +* On easy difficulty your villagers will NEVER get infected, meaning they will always die. +* On normal difficulty they will have a 50% of getting infected or dying. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index fe1c9dd8258ec8c3fdf343d4a44de2be2ae3d35f..525d702d78a609af987ebd2c32169b873e5c05ed 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -604,4 +604,9 @@ public class PaperWorldConfig { + private void nerfNetherPortalPigmen() { + nerfNetherPortalPigmen = getBoolean("game-mechanics.nerf-pigmen-from-nether-portals", nerfNetherPortalPigmen); + } ++ ++ public double zombieVillagerInfectionChance = -1.0; ++ private void zombieVillagerInfectionChance() { ++ zombieVillagerInfectionChance = getDouble("zombie-villager-infection-chance", zombieVillagerInfectionChance); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +index f406826945dd752e6528743a0c8cad3cfdfc4a95..3d8d4a43e6cd554b6f1eeafa1c8d43cef877139a 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +@@ -450,10 +450,14 @@ public class EntityZombie extends EntityMonster { + @Override + public void a(WorldServer worldserver, EntityLiving entityliving) { + super.a(worldserver, entityliving); +- if ((worldserver.getDifficulty() == EnumDifficulty.NORMAL || worldserver.getDifficulty() == EnumDifficulty.HARD) && entityliving instanceof EntityVillager) { +- if (worldserver.getDifficulty() != EnumDifficulty.HARD && this.random.nextBoolean()) { ++ // Paper start ++ if (world.paperConfig.zombieVillagerInfectionChance != 0.0 && (world.paperConfig.zombieVillagerInfectionChance != -1.0 || worldserver.getDifficulty() == EnumDifficulty.NORMAL || worldserver.getDifficulty() == EnumDifficulty.HARD) && entityliving instanceof EntityVillager) { ++ if (world.paperConfig.zombieVillagerInfectionChance == -1.0 && worldserver.getDifficulty() != EnumDifficulty.HARD && this.random.nextBoolean()) { + return; + } ++ if (world.paperConfig.zombieVillagerInfectionChance != -1.0 && (this.random.nextDouble() * 100.0) > world.paperConfig.zombieVillagerInfectionChance) { ++ return; ++ } // Paper end + + EntityVillager entityvillager = (EntityVillager) entityliving; + // CraftBukkit start diff --git a/patches/server-unmapped/0001/0418-Optimise-Chunk-getFluid.patch b/patches/server-unmapped/0001/0418-Optimise-Chunk-getFluid.patch new file mode 100644 index 0000000000..593c9aaac6 --- /dev/null +++ b/patches/server-unmapped/0001/0418-Optimise-Chunk-getFluid.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 14 Jan 2020 14:59:08 -0800 +Subject: [PATCH] Optimise Chunk#getFluid + +Removing the try catch and generally reducing ops should make it +faster on its own, however removing the try catch makes it +easier to inline due to code size + +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index fc55e89260fdec2c5045e8f00e091191980ff1f2..bb2ff043f0d159fa18769c31b08683ee12037c58 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -431,17 +431,20 @@ public class Chunk implements IChunkAccess { + } + + public Fluid a(int i, int j, int k) { +- try { +- if (j >= 0 && j >> 4 < this.sections.length) { +- ChunkSection chunksection = this.sections[j >> 4]; +- +- if (!ChunkSection.a(chunksection)) { +- return chunksection.b(i & 15, j & 15, k & 15); ++ //try { // Paper - remove try catch ++ // Paper start - reduce the number of ops in this call ++ int index = j >> 4; ++ if (index >= 0 && index < this.sections.length) { ++ ChunkSection chunksection = this.sections[index]; ++ ++ if (chunksection != null) { ++ return chunksection.blockIds.a((j & 15) << 8 | (k & 15) << 4 | i & 15).getFluid(); + } ++ // Paper end + } + + return FluidTypes.EMPTY.h(); +- } catch (Throwable throwable) { ++ /*} catch (Throwable throwable) { // Paper - remove try catch + CrashReport crashreport = CrashReport.a(throwable, "Getting fluid state"); + CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Block being got"); + +@@ -450,6 +453,7 @@ public class Chunk implements IChunkAccess { + }); + throw new ReportedException(crashreport); + } ++ */ // Paper - remove try catch + } + + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +index 4bc26a7a4ae91aac90c256758ec8868d83027c0c..973aa060d6964c7d470bc7aff89b879daf1df153 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +@@ -45,7 +45,7 @@ public class ChunkSection { + } + + public Fluid b(int i, int j, int k) { +- return ((IBlockData) this.blockIds.a(i, j, k)).getFluid(); ++ return ((IBlockData) this.blockIds.a(i, j, k)).getFluid(); // Paper - diff on change - we expect this to be effectively just getType(x, y, z).getFluid(). If this changes we need to check other patches that use IBlockData#getFluid. + } + + public void a() { diff --git a/patches/server-unmapped/0001/0419-Optimise-TickListServer-by-rewriting-it.patch b/patches/server-unmapped/0001/0419-Optimise-TickListServer-by-rewriting-it.patch new file mode 100644 index 0000000000..7ca4b60e4f --- /dev/null +++ b/patches/server-unmapped/0001/0419-Optimise-TickListServer-by-rewriting-it.patch @@ -0,0 +1,1267 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 14 Feb 2020 01:24:39 -0800 +Subject: [PATCH] Optimise TickListServer by rewriting it + +In my profiling TickListServer showed up as +~10% for saving chunks and ~5% for the scheduling +of items on a server with ~90 players at +view distance = 5. Most of the performance +loss is unneccessary. + +TickListServer has numerous performance issues: + 1. Handling scheduled items is O(nlogn) + 2. Getting scheduled items for a chunk is O(n), + with n being the the number of scheduled items + for all chunks (hits saving very hard) + 3. Checking if an item is scheduled for the current tick is O(n), + with n being the number of items scheduled for current tick + 4. Items not in ticking chunks are churned in the scheduler + +The biggest issues are 4 & 2. + +We solve 1 by splitting up scheduled items into short and long scheduled, +where we expect the vast majority of our entries to be in the short scheduled +set. Handling short scheduled items is O(n) due to how the comparison +process is reduced to mapping. See TickListServerInterval. However, +this isn't memory-efficient - which is why long scheduled exists. +Long scheduled is handled the same as TickListServer. + +2 is solved by mapping what entries are in what chunks. + +3 is solved by mapping what blocks have what scheduled for them. + +4 is solved by moving the items that are not in ticking chunks +into a map of entries for that chunk. Once the chunk is moved +to ticking, the items are re-scheduled. + +This patch has also added two flags to debug excessive tick delays: +-Dpaper.ticklist-warn-on-excessive-delay=true (false by default) +and -Dpaper.ticklist-excessive-delay-threshold=ticks which +sets the excessive tick delay to the specified ticks (defaults to +60 * 20 ticks, aka 60 seconds) + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 8bf4d2b8c38c02d6a5b2fea37113689a252f1571..da93d38fe63035e4ff198ada84a4431f52d97c01 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -354,6 +354,13 @@ public class PaperConfig { + maxBookTotalSizeMultiplier = getDouble("settings.book-size.total-multiplier", maxBookTotalSizeMultiplier); + } + ++ public static boolean useOptimizedTickList = true; ++ private static void useOptimizedTickList() { ++ if (config.contains("settings.use-optimized-ticklist")) { // don't add default, hopefully temporary config ++ useOptimizedTickList = config.getBoolean("settings.use-optimized-ticklist"); ++ } ++ } ++ + public static boolean asyncChunks = false; + private static void asyncChunks() { + ConfigurationSection section; +diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8918bad880d6eeed30db39b6326b2f65e24edf45 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +@@ -0,0 +1,628 @@ ++package com.destroystokyo.paper.server.ticklist; ++ ++import java.util.function.Function; ++import net.minecraft.CrashReport; ++import net.minecraft.CrashReportSystemDetails; ++import net.minecraft.ReportedException; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.nbt.NBTTagList; ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Comparator; ++import java.util.Iterator; ++import java.util.List; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++import net.minecraft.server.level.ChunkProviderServer; ++import net.minecraft.server.level.WorldServer; ++import net.minecraft.world.level.ChunkCoordIntPair; ++import net.minecraft.world.level.NextTickListEntry; ++import net.minecraft.world.level.TickListPriority; ++import net.minecraft.world.level.TickListServer; ++import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.levelgen.structure.StructureBoundingBox; ++ ++public final class PaperTickList extends TickListServer { // extend to avoid breaking ABI ++ ++ // in the order the state is expected to change (mostly) ++ public static final int STATE_UNSCHEDULED = 1 << 0; ++ public static final int STATE_SCHEDULED = 1 << 1; // scheduled for some tick ++ public static final int STATE_PENDING_TICK = 1 << 2; // for this tick ++ public static final int STATE_TICKING = 1 << 3; ++ public static final int STATE_TICKED = 1 << 4; // after this, it gets thrown back to unscheduled ++ public static final int STATE_CANCELLED_TICK = 1 << 5; // still gets moved to unscheduled after tick ++ ++ private static final int SHORT_SCHEDULE_TICK_THRESHOLD = 20 * 20 + 1; // 20 seconds ++ ++ private final WorldServer world; ++ private final Predicate excludeFromScheduling; ++ private final Function getMinecraftKeyFrom; ++ //private final Function getObjectFronMinecraftKey; ++ private final Consumer> tickFunction; ++ ++ private final co.aikar.timings.Timing timingCleanup; // Paper ++ private final co.aikar.timings.Timing timingTicking; // Paper ++ private final co.aikar.timings.Timing timingFinished; ++ ++ // note: remove ops / add ops suck on fastutil, a chained hashtable implementation would work better, but Long... ++ // try to alleviate with a very small load factor ++ private final Long2ObjectOpenHashMap>> entriesByBlock = new Long2ObjectOpenHashMap<>(1024, 0.25f); ++ private final Long2ObjectOpenHashMap>> entriesByChunk = new Long2ObjectOpenHashMap<>(1024, 0.25f); ++ private final Long2ObjectOpenHashMap>> pendingChunkTickLoad = new Long2ObjectOpenHashMap<>(1024, 0.5f); ++ ++ // fastutil has O(1) first/last while TreeMap/TreeSet are log(n) ++ private final ObjectRBTreeSet> longScheduled = new ObjectRBTreeSet<>(TickListServerInterval.ENTRY_COMPARATOR); ++ ++ private final ArrayDeque> toTickThisTick = new ArrayDeque<>(); ++ ++ private final TickListServerInterval[] shortScheduled = new TickListServerInterval[SHORT_SCHEDULE_TICK_THRESHOLD]; ++ { ++ for (int i = 0, len = this.shortScheduled.length; i < len; ++i) { ++ this.shortScheduled[i] = new TickListServerInterval<>(); ++ } ++ } ++ private int shortScheduledIndex; ++ ++ private long currentTick; ++ ++ private static final boolean WARN_ON_EXCESSIVE_DELAY = Boolean.getBoolean("paper.ticklist-warn-on-excessive-delay"); ++ private static final long EXCESSIVE_DELAY_THRESHOLD = Long.getLong("paper.ticklist-excessive-delay-threshold", 60 * 20).longValue(); // 1 min dfl ++ ++ // assume index < length ++ private static int getWrappedIndex(final int start, final int length, final int index) { ++ final int next = start + index; ++ return next < length ? next : next - length; ++ } ++ ++ private static int getNextIndex(final int curr, final int length) { ++ final int next = curr + 1; ++ return next < length ? next : 0; ++ } ++ ++ public PaperTickList(final WorldServer world, final Predicate excludeFromScheduling, final Function getMinecraftKeyFrom, ++ final Consumer> tickFunction, final String timingsType) { ++ super(world, excludeFromScheduling, getMinecraftKeyFrom, tickFunction, timingsType); ++ this.world = world; ++ this.excludeFromScheduling = excludeFromScheduling; ++ this.getMinecraftKeyFrom = getMinecraftKeyFrom; ++ this.tickFunction = tickFunction; ++ this.timingCleanup = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Cleanup"); // Paper ++ this.timingTicking = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Ticking"); // Paper ++ this.timingFinished = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Finish"); ++ this.currentTick = this.world.getTime(); ++ } ++ ++ private void queueEntryForTick(final NextTickListEntry entry, final ChunkProviderServer chunkProvider) { ++ if (entry.tickState == STATE_SCHEDULED) { ++ if (chunkProvider.isTickingReadyMainThread(entry.getPosition())) { ++ this.toTickThisTick.add(entry); ++ entry.tickState = STATE_PENDING_TICK; ++ } else { ++ // we dump them to a map to avoid constantly re-scheduling them ++ this.addToNotTickingReady(entry); ++ } ++ } ++ } ++ ++ private void addToNotTickingReady(final NextTickListEntry entry) { ++ this.pendingChunkTickLoad.computeIfAbsent(MCUtil.getCoordinateKey(entry.getPosition()), (long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(entry); ++ } ++ ++ private void addToSchedule(final NextTickListEntry entry) { ++ long delay = entry.getTargetTick() - (this.currentTick + 1); ++ if (delay < SHORT_SCHEDULE_TICK_THRESHOLD) { ++ if (delay < 0) { ++ // longScheduled orders by tick time, short scheduled does not ++ this.longScheduled.add(entry); ++ } else { ++ this.shortScheduled[getWrappedIndex(this.shortScheduledIndex, SHORT_SCHEDULE_TICK_THRESHOLD, (int)delay)].addEntryLast(entry); ++ } ++ } else { ++ this.longScheduled.add(entry); ++ } ++ } ++ ++ private void removeEntry(final NextTickListEntry entry) { ++ entry.tickState = STATE_CANCELLED_TICK; ++ // short/long scheduled will skip the entry ++ ++ final BlockPosition pos = entry.getPosition(); ++ final long blockKey = MCUtil.getBlockKey(pos); ++ ++ final ArrayList> currentEntries = this.entriesByBlock.get(blockKey); ++ ++ if (currentEntries.size() == 1) { ++ // it should contain our entry ++ this.entriesByBlock.remove(blockKey); ++ } else { ++ // it's more likely that this entry is at the start of the list than the end ++ for (int i = 0, len = currentEntries.size(); i < len; ++i) { ++ final NextTickListEntry currentEntry = currentEntries.get(i); ++ if (currentEntry == entry) { ++ currentEntries.remove(i); ++ break; ++ } ++ } ++ } ++ ++ final long chunkKey = MCUtil.getCoordinateKey(entry.getPosition()); ++ ++ ObjectRBTreeSet> set = this.entriesByChunk.get(chunkKey); ++ ++ set.remove(entry); ++ ++ if (set.isEmpty()) { ++ this.entriesByChunk.remove(chunkKey); ++ } ++ ++ ArrayList> pendingTickingLoad = this.pendingChunkTickLoad.get(chunkKey); ++ ++ if (pendingTickingLoad != null) { ++ for (int i = 0, len = pendingTickingLoad.size(); i < len; ++i) { ++ if (pendingTickingLoad.get(i) == entry) { ++ pendingTickingLoad.remove(i); ++ break; ++ } ++ } ++ ++ if (pendingTickingLoad.isEmpty()) { ++ this.pendingChunkTickLoad.remove(chunkKey); ++ } ++ } ++ ++ long delay = entry.getTargetTick() - (this.currentTick + 1); ++ if (delay >= SHORT_SCHEDULE_TICK_THRESHOLD) { ++ this.longScheduled.remove(entry); ++ } ++ } ++ ++ public void onChunkSetTicking(final int chunkX, final int chunkZ) { ++ final ArrayList> pending = this.pendingChunkTickLoad.remove(MCUtil.getCoordinateKey(chunkX, chunkZ)); ++ if (pending == null) { ++ return; ++ } ++ ++ for (int i = 0, size = pending.size(); i < size; ++i) { ++ final NextTickListEntry entry = pending.get(i); ++ // already in all the relevant reference maps, just need to add to longScheduled or shortScheduled ++ this.addToSchedule(entry); ++ } ++ } ++ ++ private void prepare() { ++ final long currentTick = this.currentTick; ++ ++ final ChunkProviderServer chunkProvider = this.world.getChunkProvider(); ++ ++ // here we setup what's going to tick ++ ++ // we don't remove items from shortScheduled (but do from longScheduled) because they're cleared at the end of ++ // this tick ++ if (this.longScheduled.isEmpty() || this.longScheduled.first().getTargetTick() > currentTick) { ++ // nothing in longScheduled to worry about ++ final TickListServerInterval interval = this.shortScheduled[this.shortScheduledIndex]; ++ for (int i = 0, len = interval.byPriority.length; i < len; ++i) { ++ for (final Iterator> iterator = interval.byPriority[i].iterator(); iterator.hasNext();) { ++ this.queueEntryForTick(iterator.next(), chunkProvider); ++ } ++ } ++ } else { ++ final TickListServerInterval interval = this.shortScheduled[this.shortScheduledIndex]; ++ ++ // combine interval and longScheduled, keeping order ++ final Comparator> comparator = (Comparator)TickListServerInterval.ENTRY_COMPARATOR; ++ final Iterator> longScheduledIterator = this.longScheduled.iterator(); ++ NextTickListEntry longCurrent = longScheduledIterator.next(); ++ ++ for (int i = 0, len = interval.byPriority.length; i < len; ++i) { ++ for (final Iterator> iterator = interval.byPriority[i].iterator(); iterator.hasNext();) { ++ final NextTickListEntry shortCurrent = iterator.next(); ++ if (longCurrent != null) { ++ // drain longCurrent until we can add shortCurrent ++ while (comparator.compare(longCurrent, shortCurrent) <= 0) { ++ this.queueEntryForTick(longCurrent, chunkProvider); ++ longScheduledIterator.remove(); ++ if (longScheduledIterator.hasNext()) { ++ longCurrent = longScheduledIterator.next(); ++ if (longCurrent.getTargetTick() > currentTick) { ++ longCurrent = null; ++ break; ++ } ++ } else { ++ longCurrent = null; ++ break; ++ } ++ } ++ } ++ this.queueEntryForTick(shortCurrent, chunkProvider); ++ } ++ } ++ ++ // add remaining from long scheduled ++ for (;;) { ++ if (longCurrent == null || longCurrent.getTargetTick() > currentTick) { ++ break; ++ } ++ longScheduledIterator.remove(); ++ this.queueEntryForTick(longCurrent, chunkProvider); ++ ++ if (longScheduledIterator.hasNext()) { ++ longCurrent = longScheduledIterator.next(); ++ } else { ++ break; ++ } ++ } ++ } ++ } ++ ++ private boolean warnedAboutDesync; ++ ++ @Override ++ public void nextTick() { ++ ++this.currentTick; ++ if (this.currentTick != this.world.getTime()) { ++ if (!this.warnedAboutDesync) { ++ this.warnedAboutDesync = true; ++ MinecraftServer.LOGGER.error("World tick desync detected! Expected " + this.currentTick + " ticks, but got " + this.world.getTime() + " ticks for world '" + this.world.getWorld().getName() + "'", new Throwable()); ++ MinecraftServer.LOGGER.error("Preventing redstone from breaking by refusing to accept new tick time"); ++ } ++ } ++ } ++ ++ @Override ++ public void tick() { ++ final ChunkProviderServer chunkProvider = this.world.getChunkProvider(); ++ ++ this.world.getMethodProfiler().enter("cleaning"); ++ this.timingCleanup.startTiming(); ++ ++ this.prepare(); ++ ++ // this must be done here in case something schedules in the tick code ++ this.shortScheduled[this.shortScheduledIndex].clear(); ++ this.shortScheduledIndex = getNextIndex(this.shortScheduledIndex, SHORT_SCHEDULE_TICK_THRESHOLD); ++ ++ this.timingCleanup.stopTiming(); ++ this.world.getMethodProfiler().exitEnter("ticking"); ++ this.timingTicking.startTiming(); ++ ++ for (final NextTickListEntry toTick : this.toTickThisTick) { ++ if (toTick.tickState != STATE_PENDING_TICK) { ++ // onTickEnd gets called at end of tick ++ continue; ++ } ++ try { ++ if (chunkProvider.isTickingReadyMainThread(toTick.getPosition())) { ++ toTick.tickState = STATE_TICKING; ++ this.tickFunction.accept(toTick); ++ if (toTick.tickState == STATE_TICKING) { ++ toTick.tickState = STATE_TICKED; ++ } // else it's STATE_CANCELLED_TICK ++ } else { ++ // re-schedule eventually ++ toTick.tickState = STATE_SCHEDULED; ++ this.addToNotTickingReady(toTick); ++ } ++ } catch (final Throwable thr) { ++ // start copy from TickListServer // TODO check on update ++ CrashReport crashreport = CrashReport.a(thr, "Exception while ticking"); ++ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Block being ticked"); ++ ++ CrashReportSystemDetails.a(crashreportsystemdetails, toTick.getPosition(), (IBlockData) null); ++ throw new ReportedException(crashreport); ++ // end copy from TickListServer ++ } ++ } ++ ++ this.timingTicking.stopTiming(); ++ this.world.getMethodProfiler().exit(); ++ this.timingFinished.startTiming(); ++ ++ // finished ticking, actual cleanup time ++ for (int i = 0, len = this.toTickThisTick.size(); i < len; ++i) { ++ final NextTickListEntry entry = this.toTickThisTick.poll(); ++ if (entry.tickState != STATE_SCHEDULED) { ++ // some entries get re-scheduled due to their chunk not being loaded/at correct status, so do not ++ // call onTickEnd for them ++ this.onTickEnd(entry); ++ } ++ } ++ ++ this.timingFinished.stopTiming(); ++ } ++ ++ private void onTickEnd(final NextTickListEntry entry) { ++ if (entry.tickState == STATE_CANCELLED_TICK) { ++ return; ++ } ++ entry.tickState = STATE_UNSCHEDULED; ++ ++ final BlockPosition pos = entry.getPosition(); ++ final long blockKey = MCUtil.getBlockKey(pos); ++ ++ final ArrayList> currentEntries = this.entriesByBlock.get(blockKey); ++ ++ if (currentEntries.size() == 1) { ++ // it should contain our entry ++ this.entriesByBlock.remove(blockKey); ++ } else { ++ // it's more likely that this entry is at the start of the list than the end ++ for (int i = 0, len = currentEntries.size(); i < len; ++i) { ++ final NextTickListEntry currentEntry = currentEntries.get(i); ++ if (currentEntry == entry) { ++ currentEntries.remove(i); ++ break; ++ } ++ } ++ } ++ ++ final long chunkKey = MCUtil.getCoordinateKey(entry.getPosition()); ++ ++ ObjectRBTreeSet> set = this.entriesByChunk.get(chunkKey); ++ ++ set.remove(entry); ++ ++ if (set.isEmpty()) { ++ this.entriesByChunk.remove(chunkKey); ++ } ++ ++ // already removed from longScheduled or shortScheduled ++ } ++ ++ @Override ++ public boolean isPendingTickThisTick(final BlockPosition blockposition, final T data) { ++ final ArrayList> entries = this.entriesByBlock.get(MCUtil.getBlockKey(blockposition)); ++ ++ if (entries == null) { ++ return false; ++ } ++ ++ for (int i = 0, size = entries.size(); i < size; ++i) { ++ final NextTickListEntry entry = entries.get(i); ++ if (entry.getData() == data && entry.tickState == STATE_PENDING_TICK) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public boolean isScheduledForTick(final BlockPosition blockposition, final T data) { ++ final ArrayList> entries = this.entriesByBlock.get(MCUtil.getBlockKey(blockposition)); ++ ++ if (entries == null) { ++ return false; ++ } ++ ++ for (int i = 0, size = entries.size(); i < size; ++i) { ++ final NextTickListEntry entry = entries.get(i); ++ if (entry.getData() == data && entry.tickState == STATE_SCHEDULED) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public void schedule(BlockPosition blockPosition, T t, int i, TickListPriority tickListPriority) { ++ this.schedule(blockPosition, t, i + this.currentTick, tickListPriority); ++ } ++ ++ public void schedule(final NextTickListEntry entry) { ++ this.schedule(entry.getPosition(), entry.getData(), entry.getTargetTick(), entry.getPriority()); ++ } ++ ++ public void schedule(final BlockPosition pos, final T data, final long targetTick, final TickListPriority priority) { ++ final NextTickListEntry entry = new NextTickListEntry<>(pos, data, targetTick, priority); ++ if (this.excludeFromScheduling.test(entry.getData())) { ++ return; ++ } ++ ++ if (WARN_ON_EXCESSIVE_DELAY) { ++ final long delay = entry.getTargetTick() - this.currentTick; ++ if (delay >= EXCESSIVE_DELAY_THRESHOLD) { ++ MinecraftServer.LOGGER.warn("Entry " + entry.toString() + " has been scheduled with an excessive delay of: " + delay, new Throwable()); ++ } ++ } ++ ++ final long blockKey = MCUtil.getBlockKey(pos); ++ ++ final ArrayList> currentEntries = this.entriesByBlock.computeIfAbsent(blockKey, (long keyInMap) -> new ArrayList<>(3)); ++ ++ if (currentEntries.isEmpty()) { ++ currentEntries.add(entry); ++ } else { ++ for (int i = 0, size = currentEntries.size(); i < size; ++i) { ++ final NextTickListEntry currentEntry = currentEntries.get(i); ++ ++ // entries are only blocked from scheduling if currentEntry.equals(toSchedule) && currentEntry is scheduled to tick (NOT including pending) ++ if (currentEntry.getData() == entry.getData() && currentEntry.tickState == STATE_SCHEDULED) { ++ // can't add ++ return; ++ } ++ } ++ currentEntries.add(entry); ++ } ++ ++ entry.tickState = STATE_SCHEDULED; ++ ++ this.entriesByChunk.computeIfAbsent(MCUtil.getCoordinateKey(entry.getPosition()), (final long keyInMap) -> { ++ return new ObjectRBTreeSet<>(TickListServerInterval.ENTRY_COMPARATOR); ++ }).add(entry); ++ ++ this.addToSchedule(entry); ++ } ++ ++ public void scheduleAll(final Iterator> iterator) { ++ while (iterator.hasNext()) { ++ this.schedule(iterator.next()); ++ } ++ } ++ ++ // this is not the standard interception calculation, but it's the one vanilla uses ++ // i.e the y value is ignored? the x, z calc isn't correct? ++ // however for the copy op they use the correct intersection, after using this one of course... ++ private static boolean isBlockInSortof(final StructureBoundingBox boundingBox, final BlockPosition pos) { ++ return pos.getX() >= boundingBox.getMinX() && pos.getX() < boundingBox.getMaxX() && pos.getZ() >= boundingBox.getMinZ() && pos.getZ() < boundingBox.getMaxZ(); ++ } ++ ++ @Override ++ public List> getEntriesInBoundingBox(final StructureBoundingBox structureboundingbox, final boolean removeReturned, final boolean excludeTicked) { ++ if (structureboundingbox.getMinX() == structureboundingbox.getMaxX() || structureboundingbox.getMinZ() == structureboundingbox.getMaxZ()) { ++ return Collections.emptyList(); // vanilla behaviour, check isBlockInSortof above ++ } ++ ++ final int lowerChunkX = structureboundingbox.getMinX() >> 4; ++ final int upperChunkX = (structureboundingbox.getMaxX() - 1) >> 4; // subtract 1 since maxX is exclusive ++ final int lowerChunkZ = structureboundingbox.getMinZ() >> 4; ++ final int upperChunkZ = (structureboundingbox.getMaxZ() - 1) >> 4; // subtract 1 since maxZ is exclusive ++ ++ final int xChunksLength = (upperChunkX - lowerChunkX + 1); ++ final int zChunksLength = (upperChunkZ - lowerChunkZ + 1); ++ ++ final ObjectRBTreeSet>[] containingChunks = new ObjectRBTreeSet[xChunksLength * zChunksLength]; ++ ++ final int offset = (xChunksLength * -lowerChunkZ - lowerChunkX); ++ int totalEntries = 0; ++ for (int currChunkX = lowerChunkX; currChunkX <= upperChunkX; ++currChunkX) { ++ for (int currChunkZ = lowerChunkZ; currChunkZ <= upperChunkZ; ++currChunkZ) { ++ // todo optimize ++ //final int index = (currChunkX - lowerChunkX) + xChunksLength * (currChunkZ - lowerChunkZ); ++ final int index = offset + currChunkX + xChunksLength * currChunkZ; ++ final ObjectRBTreeSet> set = containingChunks[index] = this.entriesByChunk.get(MCUtil.getCoordinateKey(currChunkX, currChunkZ)); ++ if (set != null) { ++ totalEntries += set.size(); ++ } ++ } ++ } ++ ++ final List> ret = new ArrayList<>(totalEntries); ++ ++ final int matchOne = (STATE_SCHEDULED | STATE_PENDING_TICK) | (excludeTicked ? 0 : (STATE_TICKING | STATE_TICKED)); ++ ++ MCUtil.mergeSortedSets((NextTickListEntry entry) -> { ++ if (!isBlockInSortof(structureboundingbox, entry.getPosition())) { ++ return; ++ } ++ final int tickState = entry.tickState; ++ if ((tickState & matchOne) == 0) { ++ return; ++ } ++ ++ ret.add(entry); ++ return; ++ }, TickListServerInterval.ENTRY_COMPARATOR, containingChunks); ++ ++ if (removeReturned) { ++ for (NextTickListEntry entry : ret) { ++ this.removeEntry(entry); ++ } ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public void copy(StructureBoundingBox structureboundingbox, BlockPosition blockposition) { ++ // start copy from TickListServer // TODO check on update ++ List> list = this.getEntriesInBoundingBox(structureboundingbox, false, false); ++ Iterator> iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ NextTickListEntry nextticklistentry = iterator.next(); ++ ++ if (structureboundingbox.hasPoint( nextticklistentry.getPosition())) { ++ BlockPosition blockposition1 = nextticklistentry.getPosition().add(blockposition); ++ T t0 = nextticklistentry.getData(); ++ ++ this.schedule(new NextTickListEntry<>(blockposition1, t0, nextticklistentry.getTargetTick(), nextticklistentry.getPriority())); ++ } ++ } ++ // end copy from TickListServer ++ } ++ ++ @Override ++ public List> getEntriesInChunk(ChunkCoordIntPair chunkPos, boolean removeReturned, boolean excludeTicked) { ++ // Vanilla DOES get the entries 2 blocks out of the chunk too, but that doesn't matter since we ignore chunks ++ // not at ticking status, and ticking status requires neighbours loaded ++ // so with this method we will reduce scheduler churning ++ final int matchOne = (STATE_SCHEDULED | STATE_PENDING_TICK) | (excludeTicked ? 0 : (STATE_TICKING | STATE_TICKED)); ++ ++ final ObjectRBTreeSet> entries = this.entriesByChunk.get(MCUtil.getCoordinateKey(chunkPos)); ++ ++ if (entries == null) { ++ return Collections.emptyList(); ++ } ++ ++ final List> ret = new ArrayList<>(entries.size()); ++ ++ for (NextTickListEntry entry : entries) { ++ if ((entry.tickState & matchOne) == 0) { ++ continue; ++ } ++ ret.add(entry); ++ } ++ ++ if (removeReturned) { ++ for (NextTickListEntry entry : ret) { ++ this.removeEntry(entry); ++ } ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public NBTTagList serialize(ChunkCoordIntPair chunkcoordintpair) { ++ // start copy from TickListServer // TODO check on update ++ List> list = this.getEntriesInChunk(chunkcoordintpair, false, true); ++ ++ return TickListServer.serialize(this.getMinecraftKeyFrom, list, this.currentTick); ++ // end copy from TickListServer ++ } ++ ++ @Override ++ public int getTotalScheduledEntries() { ++ // good thing this is only used in debug reports // TODO check on update ++ int ret = 0; ++ ++ for (NextTickListEntry entry : this.longScheduled) { ++ if (entry.tickState == STATE_SCHEDULED) { ++ ++ret; ++ } ++ } ++ ++ for (Iterator>>> iterator = this.pendingChunkTickLoad.long2ObjectEntrySet().iterator(); iterator.hasNext();) { ++ ArrayList> list = iterator.next().getValue(); ++ ++ for (NextTickListEntry entry : list) { ++ if (entry.tickState == STATE_SCHEDULED) { ++ ++ret; ++ } ++ } ++ } ++ ++ for (TickListServerInterval interval : this.shortScheduled) { ++ for (Iterable> set : interval.byPriority) { ++ for (NextTickListEntry entry : set) { ++ if (entry.tickState == STATE_SCHEDULED) { ++ ++ret; ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/TickListServerInterval.java b/src/main/java/com/destroystokyo/paper/server/ticklist/TickListServerInterval.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b58432a8b60670562baf00cf5279c702aaad4557 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/server/ticklist/TickListServerInterval.java +@@ -0,0 +1,41 @@ ++package com.destroystokyo.paper.server.ticklist; ++ ++import com.destroystokyo.paper.util.set.LinkedSortedSet; ++import java.util.Comparator; ++import net.minecraft.world.level.NextTickListEntry; ++import net.minecraft.world.level.TickListPriority; ++ ++// represents a set of entries to tick at a specified time ++public final class TickListServerInterval { ++ ++ public static final int TOTAL_PRIORITIES = TickListPriority.values().length; ++ public static final Comparator> ENTRY_COMPARATOR_BY_ID = (entry1, entry2) -> { ++ return Long.compare(entry1.getId(), entry2.getId()); ++ }; ++ public static final Comparator> ENTRY_COMPARATOR = (Comparator)NextTickListEntry.comparator(); ++ ++ // we do not record the interval, this class is meant to be used on a ring buffer ++ ++ // inlined enum map for TickListPriority ++ public final LinkedSortedSet>[] byPriority = new LinkedSortedSet[TOTAL_PRIORITIES]; ++ ++ { ++ for (int i = 0, len = this.byPriority.length; i < len; ++i) { ++ this.byPriority[i] = new LinkedSortedSet<>(ENTRY_COMPARATOR_BY_ID); ++ } ++ } ++ ++ public void addEntryLast(final NextTickListEntry entry) { ++ this.byPriority[entry.getPriority().ordinal()].addLast(entry); ++ } ++ ++ public void addEntryFirst(final NextTickListEntry entry) { ++ this.byPriority[entry.getPriority().ordinal()].addFirst(entry); ++ } ++ ++ public void clear() { ++ for (int i = 0, len = this.byPriority.length; i < len; ++i) { ++ this.byPriority[i].clear(); // O(1) clear ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/set/LinkedSortedSet.java b/src/main/java/com/destroystokyo/paper/util/set/LinkedSortedSet.java +new file mode 100644 +index 0000000000000000000000000000000000000000..118988c39e58f28e8a2851792b9c014f341f06fc +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/set/LinkedSortedSet.java +@@ -0,0 +1,142 @@ ++package com.destroystokyo.paper.util.set; ++ ++import java.util.Comparator; ++import java.util.Iterator; ++import java.util.NoSuchElementException; ++ ++public final class LinkedSortedSet implements Iterable { ++ ++ public final Comparator comparator; ++ ++ protected Link head; ++ protected Link tail; ++ ++ public LinkedSortedSet() { ++ this((Comparator)Comparator.naturalOrder()); ++ } ++ ++ public LinkedSortedSet(final Comparator comparator) { ++ this.comparator = comparator; ++ } ++ ++ public void clear() { ++ this.head = this.tail = null; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return new Iterator() { ++ ++ Link next = LinkedSortedSet.this.head; ++ ++ @Override ++ public boolean hasNext() { ++ return this.next != null; ++ } ++ ++ @Override ++ public E next() { ++ final Link next = this.next; ++ if (next == null) { ++ throw new NoSuchElementException(); ++ } ++ this.next = next.next; ++ return next.element; ++ } ++ }; ++ } ++ ++ public boolean addLast(final E element) { ++ final Comparator comparator = this.comparator; ++ ++ Link curr = this.tail; ++ if (curr != null) { ++ int compare; ++ ++ while ((compare = comparator.compare(element, curr.element)) < 0) { ++ Link prev = curr; ++ curr = curr.prev; ++ if (curr != null) { ++ continue; ++ } ++ this.head = prev.prev = new Link<>(element, null, prev); ++ return true; ++ } ++ ++ if (compare != 0) { ++ // insert after curr ++ final Link next = curr.next; ++ final Link insert = new Link<>(element, curr, next); ++ curr.next = insert; ++ ++ if (next == null) { ++ this.tail = insert; ++ } else { ++ next.prev = insert; ++ } ++ return true; ++ } ++ ++ return false; ++ } else { ++ this.head = this.tail = new Link<>(element); ++ return true; ++ } ++ } ++ ++ public boolean addFirst(final E element) { ++ final Comparator comparator = this.comparator; ++ ++ Link curr = this.head; ++ if (curr != null) { ++ int compare; ++ ++ while ((compare = comparator.compare(element, curr.element)) > 0) { ++ Link prev = curr; ++ curr = curr.next; ++ if (curr != null) { ++ continue; ++ } ++ this.tail = prev.next = new Link<>(element, prev, null); ++ return true; ++ } ++ ++ if (compare != 0) { ++ // insert before curr ++ final Link prev = curr.prev; ++ final Link insert = new Link<>(element, prev, curr); ++ curr.prev = insert; ++ ++ if (prev == null) { ++ this.head = insert; ++ } else { ++ prev.next = insert; ++ } ++ return true; ++ } ++ ++ return false; ++ } else { ++ this.head = this.tail = new Link<>(element); ++ return true; ++ } ++ } ++ ++ protected static final class Link { ++ public E element; ++ public Link prev; ++ public Link next; ++ ++ public Link() {} ++ ++ public Link(final E element) { ++ this.element = element; ++ } ++ ++ public Link(final E element, final Link prev, final Link next) { ++ this.element = element; ++ this.prev = prev; ++ this.next = next; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/core/BlockPosition.java b/src/main/java/net/minecraft/core/BlockPosition.java +index 8c0aeb51f5e230fd6109e750732eb54559bc9637..1fb931d4c0720a5e496030e25c865771aea3ec70 100644 +--- a/src/main/java/net/minecraft/core/BlockPosition.java ++++ b/src/main/java/net/minecraft/core/BlockPosition.java +@@ -111,6 +111,7 @@ public class BlockPosition extends BaseBlockPosition { + return i == 0 && j == 0 && k == 0 ? this : new BlockPosition(this.getX() + i, this.getY() + j, this.getZ() + k); + } + ++ public final BlockPosition add(BaseBlockPosition baseblockposition) { return this.a(baseblockposition); } // Paper - OBFHELPER + public BlockPosition a(BaseBlockPosition baseblockposition) { + return this.b(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); + } +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index df8428d95bbb2bbf96c524435151dfb45b61a9eb..db253b37a67f3e0f4b4dc32de35675ff8fae4d84 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -21,6 +21,7 @@ import net.minecraft.SystemUtils; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.SectionPosition; + import net.minecraft.network.protocol.Packet; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.progress.WorldLoadListener; + import net.minecraft.util.MathHelper; + import net.minecraft.util.profiling.GameProfilerFiller; +@@ -217,6 +218,13 @@ public class ChunkProviderServer extends IChunkProvider { + } + // Paper end + ++ // Paper start - rewrite ticklistserver ++ public final boolean isTickingReadyMainThread(BlockPosition pos) { ++ PlayerChunk chunk = this.playerChunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(pos)); ++ return chunk != null && chunk.isTickingReady(); ++ } ++ // Paper end - rewrite ticklistserver ++ + public ChunkProviderServer(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator chunkgenerator, int i, boolean flag, WorldLoadListener worldloadlistener, Supplier supplier) { + this.world = worldserver; + this.serverThreadQueue = new ChunkProviderServer.a(worldserver); +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 6433463938d8bb717840c8f57fe6e7079e1030f2..445dba8ed210407664904b707c36c78a76f25510 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -496,7 +496,9 @@ public class PlayerChunk { + PlayerChunk.this.isTickingReady = true; + + +- ++ // Paper start - rewrite ticklistserver ++ PlayerChunk.this.chunkMap.world.onChunkSetTicking(PlayerChunk.this.location.x, PlayerChunk.this.location.z); ++ // Paper end - rewrite ticklistserver + + } + }); +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 291c8091dbe58bc8e2c9ed8f3c80428386a2a1d1..12be2c5406ece16a21abaebb3ca89171304cb929 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -298,6 +298,15 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + // Paper end + ++ // Paper start - rewrite ticklistserver ++ void onChunkSetTicking(int chunkX, int chunkZ) { ++ if (com.destroystokyo.paper.PaperConfig.useOptimizedTickList) { ++ ((com.destroystokyo.paper.server.ticklist.PaperTickList) this.nextTickListBlock).onChunkSetTicking(chunkX, chunkZ); ++ ((com.destroystokyo.paper.server.ticklist.PaperTickList) this.nextTickListFluid).onChunkSetTicking(chunkX, chunkZ); ++ } ++ } ++ // Paper end - rewrite ticklistserver ++ + // Add env and gen to constructor, WorldData -> WorldDataServer + public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { + super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor +@@ -305,12 +314,21 @@ public class WorldServer extends World implements GeneratorAccessSeed { + convertable = convertable_conversionsession; + uuid = WorldUUID.getUUID(convertable_conversionsession.folder.toFile()); + // CraftBukkit end +- this.nextTickListBlock = new TickListServer<>(this, (block) -> { +- return block == null || block.getBlockData().isAir(); +- }, IRegistry.BLOCK::getKey, this::b, "Blocks"); // Paper - Timings +- this.nextTickListFluid = new TickListServer<>(this, (fluidtype) -> { +- return fluidtype == null || fluidtype == FluidTypes.EMPTY; +- }, IRegistry.FLUID::getKey, this::a, "Fluids"); // Paper - Timings ++ if (com.destroystokyo.paper.PaperConfig.useOptimizedTickList) { ++ this.nextTickListBlock = new com.destroystokyo.paper.server.ticklist.PaperTickList<>(this, (block) -> { ++ return block == null || block.getBlockData().isAir(); ++ }, IRegistry.BLOCK::getKey, this::b, "Blocks"); // Paper - Timings ++ this.nextTickListFluid = new com.destroystokyo.paper.server.ticklist.PaperTickList<>(this, (fluidtype) -> { ++ return fluidtype == null || fluidtype == FluidTypes.EMPTY; ++ }, IRegistry.FLUID::getKey, this::a, "Fluids"); // Paper - Timings ++ } else { ++ this.nextTickListBlock = new TickListServer<>(this, (block) -> { ++ return block == null || block.getBlockData().isAir(); ++ }, IRegistry.BLOCK::getKey, this::b, "Blocks"); // Paper - Timings ++ this.nextTickListFluid = new TickListServer<>(this, (fluidtype) -> { ++ return fluidtype == null || fluidtype == FluidTypes.EMPTY; ++ }, IRegistry.FLUID::getKey, this::a, "Fluids"); // Paper - Timings ++ } + this.navigators = Sets.newHashSet(); + this.L = new ObjectLinkedOpenHashSet(); + this.Q = flag1; +@@ -645,7 +663,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { + if (this.Q) { + long i = this.worldData.getTime() + 1L; + +- this.worldDataServer.setTime(i); ++ this.worldDataServer.setTime(i); // Paper - diff on change, we want the below to be ran right after this ++ this.nextTickListBlock.nextTick(); // Paper ++ this.nextTickListFluid.nextTick(); // Paper + this.worldDataServer.u().a(this.server, i); + if (this.worldData.q().getBoolean(GameRules.DO_DAYLIGHT_CYCLE)) { + this.setDayTime(this.worldData.getDayTime() + 1L); +diff --git a/src/main/java/net/minecraft/world/level/NextTickListEntry.java b/src/main/java/net/minecraft/world/level/NextTickListEntry.java +index 37b7dd82a227a88b720c13a813dd7e8caf803e03..8eb3084def3aa8776d32f8a3c942c95d24ccea3f 100644 +--- a/src/main/java/net/minecraft/world/level/NextTickListEntry.java ++++ b/src/main/java/net/minecraft/world/level/NextTickListEntry.java +@@ -6,11 +6,13 @@ import net.minecraft.core.BlockPosition; + public class NextTickListEntry { + + private static final java.util.concurrent.atomic.AtomicLong COUNTER = new java.util.concurrent.atomic.AtomicLong(); // Paper - async chunk loading +- private final T e; +- public final BlockPosition a; +- public final long b; +- public final TickListPriority c; +- private final long f; ++ private final T e; public final T getData() { return this.e; } // Paper - OBFHELPER ++ public final BlockPosition a; public final BlockPosition getPosition() { return this.a; } // Paper - OBFHELPER ++ public final long b; public final long getTargetTick() { return this.b; } // Paper - OBFHELPER ++ public final TickListPriority c; public final TickListPriority getPriority() { return this.c; } // Paper - OBFHELPER ++ private final long f; public final long getId() { return this.f; } // Paper - OBFHELPER ++ private final int hash; // Paper ++ public int tickState; // Paper + + public NextTickListEntry(BlockPosition blockposition, T t0) { + this(blockposition, t0, 0L, TickListPriority.NORMAL); +@@ -22,6 +24,7 @@ public class NextTickListEntry { + this.e = t0; + this.b = i; + this.c = ticklistpriority; ++ this.hash = this.computeHash(); // Paper + } + + public boolean equals(Object object) { +@@ -34,19 +37,31 @@ public class NextTickListEntry { + } + } + ++ // Paper start - optimize hashcode ++ @Override + public int hashCode() { ++ return this.hash; ++ } ++ public final int computeHash() { ++ // Paper end - optimize hashcode + return this.a.hashCode(); + } + +- public static Comparator a() { // Paper - decompile fix +- return Comparator.comparingLong((nextticklistentry) -> { +- return ((NextTickListEntry) nextticklistentry).b; // Paper - decompile fix +- }).thenComparing((nextticklistentry) -> { +- return ((NextTickListEntry) nextticklistentry).c; // Paper - decompile fix +- }).thenComparingLong((nextticklistentry) -> { +- return ((NextTickListEntry) nextticklistentry).f; // Paper - decompile fix +- }); ++ // Paper start - let's not use more functional code for no reason. ++ public static Comparator comparator() { return NextTickListEntry.a(); } // Paper - OBFHELPER ++ public static Comparator a() { ++ return (Comparator)(Comparator)(NextTickListEntry nextticklistentry, NextTickListEntry nextticklistentry1) -> { ++ int i = Long.compare(nextticklistentry.getTargetTick(), nextticklistentry1.getTargetTick()); ++ ++ if (i != 0) { ++ return i; ++ } else { ++ i = nextticklistentry.getPriority().compareTo(nextticklistentry1.getPriority()); ++ return i != 0 ? i : Long.compare(nextticklistentry.getId(), nextticklistentry1.getId()); ++ } ++ }; + } ++ // Paper end - let's not use more functional code for no reason. + + public String toString() { + return this.e + ": " + this.a + ", " + this.b + ", " + this.c + ", " + this.f; +diff --git a/src/main/java/net/minecraft/world/level/TickListChunk.java b/src/main/java/net/minecraft/world/level/TickListChunk.java +index c3cb513d0d107ecb43e98960b25054626aa6a03f..fd293e11ec62a41a53c1e5238cb1219349d446d4 100644 +--- a/src/main/java/net/minecraft/world/level/TickListChunk.java ++++ b/src/main/java/net/minecraft/world/level/TickListChunk.java +@@ -9,6 +9,7 @@ import net.minecraft.core.BlockPosition; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTTagList; + import net.minecraft.resources.MinecraftKey; ++import net.minecraft.server.MinecraftServer; + + public class TickListChunk implements TickList { + +@@ -61,6 +62,8 @@ public class TickListChunk implements TickList { + return nbttaglist; + } + ++ private static final int MAX_TICK_DELAY = Integer.getInteger("paper.ticklist-max-tick-delay", -1).intValue(); // Paper - clean up broken entries ++ + public static TickListChunk a(NBTTagList nbttaglist, Function function, Function function1) { + List> list = Lists.newArrayList(); + +@@ -71,7 +74,14 @@ public class TickListChunk implements TickList { + if (t0 != null) { + BlockPosition blockposition = new BlockPosition(nbttagcompound.getInt("x"), nbttagcompound.getInt("y"), nbttagcompound.getInt("z")); + +- list.add(new TickListChunk.a<>(t0, blockposition, nbttagcompound.getInt("t"), TickListPriority.a(nbttagcompound.getInt("p")))); ++ // Paper start - clean up broken entries ++ int delay = nbttagcompound.getInt("t"); ++ if (MAX_TICK_DELAY > 0 && delay > MAX_TICK_DELAY) { ++ MinecraftServer.LOGGER.warn("Dropping tick for pos " + blockposition + ", tick delay " + delay); ++ continue; ++ } ++ list.add(new TickListChunk.a<>(t0, blockposition, delay, TickListPriority.a(nbttagcompound.getInt("p")))); ++ // Paper end - clean up broken entries + } + } + +diff --git a/src/main/java/net/minecraft/world/level/TickListServer.java b/src/main/java/net/minecraft/world/level/TickListServer.java +index c221e5caf518b8c588390e438346fa58fa8c5a38..4fd89bbe6ce578fd3a166bcfbbe41908a7bb4753 100644 +--- a/src/main/java/net/minecraft/world/level/TickListServer.java ++++ b/src/main/java/net/minecraft/world/level/TickListServer.java +@@ -50,7 +50,16 @@ public class TickListServer implements TickList { + private final co.aikar.timings.Timing timingTicking; // Paper + // Paper end + ++ // Paper start ++ public void nextTick() {} ++ // Paper end ++ + public void b() { ++ // Paper start - allow overriding ++ this.tick(); ++ } ++ public void tick() { ++ // Paper end + int i = this.nextTickList.size(); + + if (false) { // CraftBukkit +@@ -118,10 +127,20 @@ public class TickListServer implements TickList { + + @Override + public boolean b(BlockPosition blockposition, T t0) { ++ // Paper start - allow overriding ++ return this.isPendingTickThisTick(blockposition, t0); ++ } ++ public boolean isPendingTickThisTick(BlockPosition blockposition, T t0) { ++ // Paper end + return this.f.contains(new NextTickListEntry<>(blockposition, t0)); + } + + public List> a(ChunkCoordIntPair chunkcoordintpair, boolean flag, boolean flag1) { ++ // Paper start - allow overriding ++ return this.getEntriesInChunk(chunkcoordintpair, flag, flag1); ++ } ++ public List> getEntriesInChunk(ChunkCoordIntPair chunkcoordintpair, boolean flag, boolean flag1) { ++ // Paper end + int i = (chunkcoordintpair.x << 4) - 2; + int j = i + 16 + 2; + int k = (chunkcoordintpair.z << 4) - 2; +@@ -131,6 +150,11 @@ public class TickListServer implements TickList { + } + + public List> a(StructureBoundingBox structureboundingbox, boolean flag, boolean flag1) { ++ // Paper start - allow overriding ++ return this.getEntriesInBoundingBox(structureboundingbox, flag, flag1); ++ } ++ public List> getEntriesInBoundingBox(StructureBoundingBox structureboundingbox, boolean flag, boolean flag1) { ++ // Paper end + List> list = this.a((List) null, this.nextTickList, structureboundingbox, flag); + + if (flag && list != null) { +@@ -170,6 +194,11 @@ public class TickListServer implements TickList { + } + + public void a(StructureBoundingBox structureboundingbox, BlockPosition blockposition) { ++ // Paper start - allow overriding ++ this.copy(structureboundingbox, blockposition); ++ } ++ public void copy(StructureBoundingBox structureboundingbox, BlockPosition blockposition) { ++ // Paper end + List> list = this.a(structureboundingbox, false, false); + Iterator iterator = list.iterator(); + +@@ -187,11 +216,17 @@ public class TickListServer implements TickList { + } + + public NBTTagList a(ChunkCoordIntPair chunkcoordintpair) { ++ // Paper start - allow overriding ++ return this.serialize(chunkcoordintpair); ++ } ++ public NBTTagList serialize(ChunkCoordIntPair chunkcoordintpair) { ++ // Paper end + List> list = this.a(chunkcoordintpair, false, true); + + return a(this.b, list, this.e.getTime()); + } + ++ public static NBTTagList serialize(Function function, Iterable> iterable, long i) { return TickListServer.a(function, iterable, i); } // Paper - OBFHELPER + private static NBTTagList a(Function function, Iterable> iterable, long i) { + NBTTagList nbttaglist = new NBTTagList(); + Iterator iterator = iterable.iterator(); +@@ -214,11 +249,21 @@ public class TickListServer implements TickList { + + @Override + public boolean a(BlockPosition blockposition, T t0) { ++ // Paper start - allow overriding ++ return this.isScheduledForTick(blockposition, t0); ++ } ++ public boolean isScheduledForTick(BlockPosition blockposition, T t0) { ++ // Paper end + return this.nextTickListHash.contains(new NextTickListEntry<>(blockposition, t0)); + } + + @Override + public void a(BlockPosition blockposition, T t0, int i, TickListPriority ticklistpriority) { ++ // Paper start - allow overriding ++ this.schedule(blockposition, t0, i, ticklistpriority); ++ } ++ public void schedule(BlockPosition blockposition, T t0, int i, TickListPriority ticklistpriority) { ++ // Paper end + if (!this.a.test(t0)) { + this.a(new NextTickListEntry<>(blockposition, t0, (long) i + this.e.getTime(), ticklistpriority)); + } +@@ -234,6 +279,11 @@ public class TickListServer implements TickList { + } + + public int a() { ++ // Paper start - allow overriding ++ return this.getTotalScheduledEntries(); ++ } ++ public int getTotalScheduledEntries() { ++ // Paper end + return this.nextTickListHash.size(); + } + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureBoundingBox.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureBoundingBox.java +index b5d6c8163c686c31375fb645d7721af06c01df40..fb4b8d7167ad7f1d24d40bbbda5f52e278f25895 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureBoundingBox.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureBoundingBox.java +@@ -8,12 +8,12 @@ import net.minecraft.nbt.NBTTagIntArray; + + public class StructureBoundingBox { + +- public int a; +- public int b; +- public int c; +- public int d; +- public int e; +- public int f; ++ public int a; public final int getMinX() { return this.a; } // Paper - OBFHELPER ++ public int b; public final int getMinY() { return this.b; } // Paper - OBFHELPER ++ public int c; public final int getMinZ() { return this.c; } // Paper - OBFHELPER ++ public int d; public final int getMaxX() { return this.d; } // Paper - OBFHELPER ++ public int e; public final int getMaxY() { return this.e; } // Paper - OBFHELPER ++ public int f; public final int getMaxZ() { return this.f; } // Paper - OBFHELPER + + public StructureBoundingBox() {} + +@@ -92,6 +92,7 @@ public class StructureBoundingBox { + this.e = 512; + } + ++ public final boolean intersects(StructureBoundingBox boundingBox) { return this.b(boundingBox); } // Paper - OBFHELPER + public boolean b(StructureBoundingBox structureboundingbox) { + return this.d >= structureboundingbox.a && this.a <= structureboundingbox.d && this.f >= structureboundingbox.c && this.c <= structureboundingbox.f && this.e >= structureboundingbox.b && this.b <= structureboundingbox.e; + } +@@ -126,6 +127,7 @@ public class StructureBoundingBox { + this.a(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); + } + ++ public final boolean hasPoint(BaseBlockPosition baseblockposition) { return this.b(baseblockposition); } // Paper - OBFHELPER + public boolean b(BaseBlockPosition baseblockposition) { + return baseblockposition.getX() >= this.a && baseblockposition.getX() <= this.d && baseblockposition.getZ() >= this.c && baseblockposition.getZ() <= this.f && baseblockposition.getY() >= this.b && baseblockposition.getY() <= this.e; + } diff --git a/patches/server-unmapped/0001/0420-Pillager-patrol-spawn-settings-and-per-player-option.patch b/patches/server-unmapped/0001/0420-Pillager-patrol-spawn-settings-and-per-player-option.patch new file mode 100644 index 0000000000..238c551c3a --- /dev/null +++ b/patches/server-unmapped/0001/0420-Pillager-patrol-spawn-settings-and-per-player-option.patch @@ -0,0 +1,151 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 525d702d78a609af987ebd2c32169b873e5c05ed..6c8e9d498c9a30a1aa88494ba09c3cae012a8fa1 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -582,10 +582,21 @@ public class PaperWorldConfig { + } + + public boolean disablePillagerPatrols = false; ++ public double patrolSpawnChance = 0.2; ++ public boolean patrolPerPlayerDelay = false; ++ public int patrolDelay = 12000; ++ public boolean patrolPerPlayerStart = false; ++ public int patrolStartDay = 5; + private void pillagerSettings() { + disablePillagerPatrols = getBoolean("game-mechanics.disable-pillager-patrols", disablePillagerPatrols); ++ patrolSpawnChance = getDouble("game-mechanics.pillager-patrols.spawn-chance", patrolSpawnChance); ++ patrolPerPlayerDelay = getBoolean("game-mechanics.pillager-patrols.spawn-delay.per-player", patrolPerPlayerDelay); ++ patrolDelay = getInt("game-mechanics.pillager-patrols.spawn-delay.ticks", patrolDelay); ++ patrolPerPlayerStart = getBoolean("game-mechanics.pillager-patrols.start.per-player", patrolPerPlayerStart); ++ patrolStartDay = getInt("game-mechanics.pillager-patrols.start.day", patrolStartDay); + } + ++ + public boolean entitiesTargetWithFollowRange = false; + private void entitiesTargetWithFollowRange() { + entitiesTargetWithFollowRange = getBoolean("entities-target-with-follow-range", entitiesTargetWithFollowRange); +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index f242330bcf3d63490b5e7be36f8af6eccfb07820..a37c868addc2e4aab8eb28d92b1c9b3e0c0fc4a9 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -215,6 +215,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public boolean viewingCredits; + private int containerUpdateDelay; // Paper + public long loginTime; // Paper ++ public int patrolSpawnDelay; // Paper - per player patrol spawns + // Paper start - cancellable death event + public boolean queueHealthUpdatePacket = false; + public net.minecraft.network.protocol.game.PacketPlayOutUpdateHealth queuedHealthUpdatePacket; +diff --git a/src/main/java/net/minecraft/stats/StatisticWrapper.java b/src/main/java/net/minecraft/stats/StatisticWrapper.java +index c1a694c4a773a41cdefca6b154711f7fc0a7fcaa..00d79ccf9c65acadc030ab1796cff4598392cb6a 100644 +--- a/src/main/java/net/minecraft/stats/StatisticWrapper.java ++++ b/src/main/java/net/minecraft/stats/StatisticWrapper.java +@@ -28,6 +28,7 @@ public class StatisticWrapper implements Iterable> { + return this.b.values().iterator(); + } + ++ public final Statistic get(T t) { return this.b(t); }; // Paper - OBFHELPER + public Statistic b(T t0) { + return this.a(t0, Counter.DEFAULT); + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPatrol.java b/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPatrol.java +index cba98adb7f2711fb97c7e4120d962f46a59682e7..111c4c1fad2f1839a8c6b7c277cf801236ae1685 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPatrol.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPatrol.java +@@ -3,7 +3,9 @@ package net.minecraft.world.level.levelgen; + import java.util.Random; + import net.minecraft.core.BlockPosition; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.WorldServer; ++import net.minecraft.stats.StatisticList; + import net.minecraft.world.entity.EntityTypes; + import net.minecraft.world.entity.EnumMobSpawn; + import net.minecraft.world.entity.GroupDataEntity; +@@ -20,13 +22,13 @@ import net.minecraft.world.level.block.state.IBlockData; + + public class MobSpawnerPatrol implements MobSpawner { + +- private int a; ++ private int a;private int getSpawnDelay() { return a; } private void setSpawnDelay(int spawnDelay) { this.a = spawnDelay; } // Paper - OBFHELPER + + public MobSpawnerPatrol() {} + + @Override + public int a(WorldServer worldserver, boolean flag, boolean flag1) { +- if (worldserver.paperConfig.disablePillagerPatrols) return 0; // Paper ++ if (worldserver.paperConfig.disablePillagerPatrols || worldserver.paperConfig.patrolSpawnChance == 0) return 0; // Paper + if (!flag) { + return 0; + } else if (!worldserver.getGameRules().getBoolean(GameRules.DO_PATROL_SPAWNING)) { +@@ -34,23 +36,51 @@ public class MobSpawnerPatrol implements MobSpawner { + } else { + Random random = worldserver.random; + +- --this.a; +- if (this.a > 0) { ++ // Paper start - Patrol settings ++ // Random player selection moved up for per player spawning and configuration ++ int j = worldserver.getPlayers().size(); ++ if (j < 1) { + return 0; ++ } ++ ++ EntityPlayer entityhuman = worldserver.getPlayers().get(random.nextInt(j)); ++ if (entityhuman.isSpectator()) { ++ return 0; ++ } ++ ++ int patrolSpawnDelay; ++ if (worldserver.paperConfig.patrolPerPlayerDelay) { ++ --entityhuman.patrolSpawnDelay; ++ patrolSpawnDelay = entityhuman.patrolSpawnDelay; + } else { +- this.a += 12000 + random.nextInt(1200); +- long i = worldserver.getDayTime() / 24000L; ++ setSpawnDelay(getSpawnDelay() - 1); ++ patrolSpawnDelay = getSpawnDelay(); ++ } ++ ++ if (patrolSpawnDelay > 0) { ++ return 0; ++ } else { ++ long days; ++ if (worldserver.paperConfig.patrolPerPlayerStart) { ++ days = entityhuman.getStatisticManager().getStatisticValue(StatisticList.CUSTOM.get(StatisticList.PLAY_ONE_MINUTE)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang ++ } else { ++ days = worldserver.getDayTime() / 24000L; ++ } ++ if (worldserver.paperConfig.patrolPerPlayerDelay) { ++ entityhuman.patrolSpawnDelay += worldserver.paperConfig.patrolDelay + random.nextInt(1200); ++ } else { ++ setSpawnDelay(getSpawnDelay() + worldserver.paperConfig.patrolDelay + random.nextInt(1200)); ++ } + +- if (i >= 5L && worldserver.isDay()) { +- if (random.nextInt(5) != 0) { ++ if (days >= worldserver.paperConfig.patrolStartDay && worldserver.isDay()) { ++ if (random.nextDouble() >= worldserver.paperConfig.patrolSpawnChance) { ++ // Paper end + return 0; + } else { +- int j = worldserver.getPlayers().size(); + + if (j < 1) { + return 0; + } else { +- EntityHuman entityhuman = (EntityHuman) worldserver.getPlayers().get(random.nextInt(j)); + + if (entityhuman.isSpectator()) { + return 0; diff --git a/patches/server-unmapped/0001/0421-Ensure-Entity-is-never-double-registered.patch b/patches/server-unmapped/0001/0421-Ensure-Entity-is-never-double-registered.patch new file mode 100644 index 0000000000..d92d3aaeba --- /dev/null +++ b/patches/server-unmapped/0001/0421-Ensure-Entity-is-never-double-registered.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 29 Mar 2020 18:26:14 -0400 +Subject: [PATCH] Ensure Entity is never double registered + +If something calls register twice, and the world is ticking, it could be +enqueued to add twice. + +Vs behavior of non ticking of just overwriting state. + +We will now simply log a warning when this happens instead of crashing the server. + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 12be2c5406ece16a21abaebb3ca89171304cb929..7dbb7e75c10d443fe9c90eadec8e9cdf01024548 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -649,6 +649,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + Entity entity2; + + while ((entity2 = (Entity) this.entitiesToAdd.poll()) != null) { ++ if (!entity2.isQueuedForRegister) continue; // Paper - ignore cancelled registers + this.registerEntity(entity2); + } + +@@ -1406,6 +1407,19 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + public void unregisterEntity(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot ++ // Paper start - fix entity registration issues ++ if (entity instanceof EntityComplexPart) { ++ // Usually this is a no-op for complex parts, and ID's should be removed, but go ahead and remove it anyways ++ // Dragon parts are handled special in register. they don't receive a valid = true or register by UUID etc. ++ this.entitiesById.remove(entity.getId(), entity); ++ return; ++ } ++ if (!entity.valid) { ++ // Someone called remove before we ever got added, cancel the add. ++ entity.isQueuedForRegister = false; ++ return; ++ } ++ // Paper end + // Spigot start + if ( entity instanceof EntityHuman ) + { +@@ -1472,9 +1486,21 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + private void registerEntity(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot ++ // Paper start - don't double enqueue entity registration ++ //noinspection ObjectEquality ++ if (this.entitiesById.get(entity.getId()) == entity) { ++ LOGGER.error(entity + " was already registered!"); ++ new Throwable().printStackTrace(); ++ return; ++ } ++ // Paper end + if (this.tickingEntities) { +- this.entitiesToAdd.add(entity); ++ if (!entity.isQueuedForRegister) { // Paper ++ this.entitiesToAdd.add(entity); ++ entity.isQueuedForRegister = true; // Paper ++ } + } else { ++ entity.isQueuedForRegister = false; // Paper + this.entitiesById.put(entity.getId(), entity); + if (entity instanceof EntityEnderDragon) { + EntityComplexPart[] aentitycomplexpart = ((EntityEnderDragon) entity).eJ(); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 68fdb01c3f11c3b060d3d621099d67f6b29431d6..f95aa9b4cc53c1e3258b7b32249ec1c3ef4ae2f1 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -148,6 +148,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + // Paper start ++ public boolean isQueuedForRegister = false; + public static Random SHARED_RANDOM = new Random() { + private boolean locked = false; + @Override diff --git a/patches/server-unmapped/0001/0422-Fix-unregistering-entities-from-unloading-chunks.patch b/patches/server-unmapped/0001/0422-Fix-unregistering-entities-from-unloading-chunks.patch new file mode 100644 index 0000000000..26ec699415 --- /dev/null +++ b/patches/server-unmapped/0001/0422-Fix-unregistering-entities-from-unloading-chunks.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 31 Mar 2020 03:01:45 -0400 +Subject: [PATCH] Fix unregistering entities from unloading chunks + +CraftBukkit caused a regression here by making unloading chunks not +have a ticket added and returning unloaded future. + +This caused entities who were killed in same tick their chunk is unloading +to not be able to be removed from the chunk. + +This then results in dead entities lingering in the Chunk. + +Combine that with a buggy detail of the previous implementation of +the Dupe UUID patch, then this was the likely source of the "Ghost entities" + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 7dbb7e75c10d443fe9c90eadec8e9cdf01024548..49a4c2ab35e00cc30bcfad7c702f9278db7b1155 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1565,9 +1565,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + private void removeEntityFromChunk(Entity entity) { +- IChunkAccess ichunkaccess = chunkProvider.getChunkUnchecked(entity.chunkX, entity.chunkZ); // CraftBukkit - SPIGOT-5228: getChunkAt won't find the entity's chunk if it has already been unloaded (i.e. if it switched to state INACCESSIBLE). ++ Chunk ichunkaccess = entity.getCurrentChunk(); // Paper - getChunkAt(x,z,full,false) is broken by CraftBukkit as it won't return an unloading chunk. Use our current chunk reference as this points to what chunk they need to be removed from anyways + +- if (ichunkaccess instanceof Chunk) { ++ if (ichunkaccess != null) { // Paper + ((Chunk) ichunkaccess).b(entity); + } + diff --git a/patches/server-unmapped/0001/0423-Remote-Connections-shouldn-t-hold-up-shutdown.patch b/patches/server-unmapped/0001/0423-Remote-Connections-shouldn-t-hold-up-shutdown.patch new file mode 100644 index 0000000000..f765b7f7b5 --- /dev/null +++ b/patches/server-unmapped/0001/0423-Remote-Connections-shouldn-t-hold-up-shutdown.patch @@ -0,0 +1,25 @@ +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 2d42b863b3fd83d1ee0532d1fcb63861641ec47b..557f80accfa36b495c9a8cffdab2e248c1cbb514 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -400,11 +400,11 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + } + + if (this.remoteControlListener != null) { +- this.remoteControlListener.b(); ++ //this.remoteControlListener.b(); // Paper - don't wait for remote connections + } + + if (this.remoteStatusListener != null) { +- this.remoteStatusListener.b(); ++ //this.remoteStatusListener.b(); // Paper - don't wait for remote connections + } + + System.exit(0); // CraftBukkit diff --git a/patches/server-unmapped/0001/0424-Do-not-allow-bees-to-load-chunks-for-beehives.patch b/patches/server-unmapped/0001/0424-Do-not-allow-bees-to-load-chunks-for-beehives.patch new file mode 100644 index 0000000000..290489e398 --- /dev/null +++ b/patches/server-unmapped/0001/0424-Do-not-allow-bees-to-load-chunks-for-beehives.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Tue, 17 Mar 2020 14:18:50 -0500 +Subject: [PATCH] Do not allow bees to load chunks for beehives + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityBee.java b/src/main/java/net/minecraft/world/entity/animal/EntityBee.java +index 7ce8eaeb9af3547869f467910b6a458118c63c1f..1d1f71a995a99b2101891a7a5bda7bec5d67f118 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityBee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityBee.java +@@ -358,6 +358,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + if (this.hivePos == null) { + return false; + } else { ++ if (!this.world.isLoadedAndInBounds(hivePos)) return false; // Paper + TileEntity tileentity = this.world.getTileEntity(this.hivePos); + + return tileentity instanceof TileEntityBeehive && ((TileEntityBeehive) tileentity).d(); +@@ -390,6 +391,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + } + + private boolean i(BlockPosition blockposition) { ++ if (!this.world.isLoadedAndInBounds(blockposition)) return false; // Paper + TileEntity tileentity = this.world.getTileEntity(blockposition); + + return tileentity instanceof TileEntityBeehive ? !((TileEntityBeehive) tileentity).isFull() : false; +@@ -632,6 +634,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + @Override + public boolean g() { + if (EntityBee.this.hasHivePos() && EntityBee.this.fd() && EntityBee.this.hivePos.a((IPosition) EntityBee.this.getPositionVector(), 2.0D)) { ++ if (!EntityBee.this.world.isLoadedAndInBounds(EntityBee.this.hivePos)) return false; // Paper + TileEntity tileentity = EntityBee.this.world.getTileEntity(EntityBee.this.hivePos); + + if (tileentity instanceof TileEntityBeehive) { +@@ -655,6 +658,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + + @Override + public void c() { ++ if (!EntityBee.this.world.isLoadedAndInBounds(EntityBee.this.hivePos)) return; // Paper + TileEntity tileentity = EntityBee.this.world.getTileEntity(EntityBee.this.hivePos); + + if (tileentity instanceof TileEntityBeehive) { diff --git a/patches/server-unmapped/0001/0425-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch b/patches/server-unmapped/0001/0425-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch new file mode 100644 index 0000000000..ccc10756ca --- /dev/null +++ b/patches/server-unmapped/0001/0425-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch @@ -0,0 +1,48 @@ +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/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 4be5f3be285b1944eee66684c1a565ac1eceb024..12cfe9f3c89316557e94c8b944b4f82277fb8877 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -1495,6 +1495,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + protected 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.world != this.world || this.trackedEntities.containsKey(entity.getId())) { ++ new Throwable("[ERROR] Illegal PlayerChunkMap::addEntity for world " + this.world.getWorld().getName() ++ + ": " + entity + (this.trackedEntities.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : "")) ++ .printStackTrace(); ++ return; ++ } ++ // Paper end + if (!(entity instanceof EntityComplexPart)) { + EntityTypes entitytypes = entity.getEntityType(); + int i = entitytypes.getChunkRange() * 16; +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 49a4c2ab35e00cc30bcfad7c702f9278db7b1155..5d085321414134043e52d8012e12a8891529097c 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1531,7 +1531,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + } + +- this.getChunkProvider().addEntity(entity); ++ // this.getChunkProvider().addEntity(entity); // Paper - moved down below valid=true + // CraftBukkit start - SPIGOT-5278 + if (entity instanceof EntityDrowned) { + this.navigators.add(((EntityDrowned) entity).navigationWater); +@@ -1542,6 +1542,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.navigators.add(((EntityInsentient) entity).getNavigation()); + } + entity.valid = true; // CraftBukkit ++ this.getChunkProvider().addEntity(entity); // Paper - from above to be below valid=true + // Paper start - Set origin location when the entity is being added to the world + if (entity.origin == null) { + entity.origin = entity.getBukkitEntity().getLocation(); diff --git a/patches/server-unmapped/0001/0426-Optimize-Collision-to-not-load-chunks.patch b/patches/server-unmapped/0001/0426-Optimize-Collision-to-not-load-chunks.patch new file mode 100644 index 0000000000..96dd9daa1f --- /dev/null +++ b/patches/server-unmapped/0001/0426-Optimize-Collision-to-not-load-chunks.patch @@ -0,0 +1,161 @@ +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 c5116a9c596074a33c98d29bb1e9cf22a8ad53bf..84c2bc0bac4f388094693859ab7b6ced5e315c27 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -61,6 +61,7 @@ import net.minecraft.server.ScoreboardServer; + import net.minecraft.server.level.DemoPlayerInteractManager; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.PlayerInteractManager; ++import net.minecraft.server.level.TicketType; + import net.minecraft.server.level.WorldServer; + import net.minecraft.server.network.PlayerConnection; + import net.minecraft.sounds.SoundCategory; +@@ -74,6 +75,7 @@ import net.minecraft.world.effect.MobEffect; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityTypes; + import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.EnumGamemode; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.World; +@@ -809,6 +811,7 @@ public abstract class PlayerList { + entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + // CraftBukkit end + ++ worldserver1.getChunkProvider().addTicket(TicketType.POST_TELEPORT, new ChunkCoordIntPair(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper + while (avoidSuffocation && !worldserver1.getCubes(entityplayer1) && entityplayer1.locY() < 256.0D) { + entityplayer1.setPosition(entityplayer1.locX(), entityplayer1.locY() + 1.0D, entityplayer1.locZ()); + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f95aa9b4cc53c1e3258b7b32249ec1c3ef4ae2f1..7bce3722fb00194f5a913c0b9866b73cfc74611d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -169,6 +169,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + private CraftEntity bukkitEntity; + + PlayerChunkMap.EntityTracker tracker; // Paper ++ public boolean collisionLoadChunks = false; // Paper + public Throwable addedToWorldStack; // Paper - entity debug + public CraftEntity getBukkitEntity() { + if (bukkitEntity == null) { +diff --git a/src/main/java/net/minecraft/world/level/ICollisionAccess.java b/src/main/java/net/minecraft/world/level/ICollisionAccess.java +index fcf6cc86e3b5d9afe3ab3b3fba2ec13846ed0b4c..fcb3e2f9dea97138e0fd4cd2eb11b54799e1d3b5 100644 +--- a/src/main/java/net/minecraft/world/level/ICollisionAccess.java ++++ b/src/main/java/net/minecraft/world/level/ICollisionAccess.java +@@ -54,7 +54,9 @@ public interface ICollisionAccess extends IBlockAccess { + } + + default boolean b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ try { if (entity != null) entity.collisionLoadChunks = true; // Paper + return this.d(entity, axisalignedbb, predicate).allMatch(VoxelShape::isEmpty); ++ } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper + } + + Stream c(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate); +diff --git a/src/main/java/net/minecraft/world/level/VoxelShapeSpliterator.java b/src/main/java/net/minecraft/world/level/VoxelShapeSpliterator.java +index fa3421c9cd8531618827627e9c524a8df77c4c58..d0cc8677f2be422722160fee9b71894b5ddd3186 100644 +--- a/src/main/java/net/minecraft/world/level/VoxelShapeSpliterator.java ++++ b/src/main/java/net/minecraft/world/level/VoxelShapeSpliterator.java +@@ -7,6 +7,9 @@ import java.util.function.Consumer; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.CursorPosition; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.RegionLimitedWorldAccess; + import net.minecraft.util.MathHelper; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.block.Blocks; +@@ -21,13 +24,13 @@ import net.minecraft.world.phys.shapes.VoxelShapes; + public class VoxelShapeSpliterator extends AbstractSpliterator { + + @Nullable +- private final Entity a; ++ private final Entity a; final Entity getEntity() { return this.a; } // Paper - OBFHELPER + private final AxisAlignedBB b; + private final VoxelShapeCollision c; + private final CursorPosition d; +- private final BlockPosition.MutableBlockPosition e; ++ private final BlockPosition.MutableBlockPosition e; final BlockPosition.MutableBlockPosition getMutablePos() { return this.e; } // Paper - OBFHELPER + private final VoxelShape f; +- private final ICollisionAccess g; ++ private final ICollisionAccess g; final ICollisionAccess getCollisionAccess() { return this.g; } // Paper - OBFHELPER + private boolean h; + private final BiPredicate i; + +@@ -64,23 +67,37 @@ public class VoxelShapeSpliterator extends AbstractSpliterator { + boolean a(Consumer consumer) { + while (true) { + if (this.d.a()) { +- int i = this.d.b(); +- int j = this.d.c(); +- int k = this.d.d(); ++ int i = this.d.b(); final int x = i; ++ int j = this.d.c(); final int y = j; ++ int k = this.d.d(); final int z = k; + int l = this.d.e(); + + if (l == 3) { + continue; + } + +- IBlockAccess iblockaccess = this.a(i, k); +- +- if (iblockaccess == null) { ++ // Paper start - ensure we don't load chunks ++ Entity entity = this.getEntity(); ++ BlockPosition.MutableBlockPosition blockposition_mutableblockposition = this.getMutablePos(); ++ boolean far = entity != null && MCUtil.distanceSq(entity.locX(), y, entity.locZ(), x, y, z) > 14; ++ blockposition_mutableblockposition.setValues(x, y, z); ++ ++ boolean isRegionLimited = this.getCollisionAccess() instanceof RegionLimitedWorldAccess; ++ IBlockData iblockdata = isRegionLimited ? Blocks.VOID_AIR.getBlockData() : ((!far && entity instanceof EntityPlayer) || (entity != null && entity.collisionLoadChunks) ++ ? this.getCollisionAccess().getType(blockposition_mutableblockposition) ++ : this.getCollisionAccess().getTypeIfLoaded(blockposition_mutableblockposition) ++ ); ++ ++ if (iblockdata == null) { ++ if (!(entity instanceof EntityPlayer) || entity.world.paperConfig.preventMovingIntoUnloadedChunks) { ++ VoxelShape voxelshape3 = VoxelShapes.of(far ? entity.getBoundingBox() : new AxisAlignedBB(new BlockPosition(x, y, z))); ++ consumer.accept(voxelshape3); ++ return true; ++ } + continue; + } +- +- this.e.d(i, j, k); +- IBlockData iblockdata = iblockaccess.getType(this.e); ++ // Paper - moved up ++ // Paper end + + if (!this.i.test(iblockdata, this.e) || l == 1 && !iblockdata.d() || l == 2 && !iblockdata.a(Blocks.MOVING_PISTON)) { + continue; +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +index cf32a4f63e8e59535c02a3f9c57f98833a2b0e83..24ecac40625629b0bbe460e7fc984b147ede1f1f 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +@@ -249,7 +249,8 @@ public final class VoxelShapes { + + if (k2 < 3) { + blockposition_mutableblockposition.a(enumaxiscycle1, i2, j2, l1); +- IBlockData iblockdata = iworldreader.getType(blockposition_mutableblockposition); ++ IBlockData iblockdata = iworldreader.getTypeIfLoaded(blockposition_mutableblockposition); // Paper ++ if (iblockdata == null) return 0.0D; // Paper + + if ((k2 != 1 || iblockdata.d()) && (k2 != 2 || iblockdata.a(Blocks.MOVING_PISTON))) { + d0 = iblockdata.b((IBlockAccess) iworldreader, blockposition_mutableblockposition, voxelshapecollision).a(enumdirection_enumaxis2, axisalignedbb.d((double) (-blockposition_mutableblockposition.getX()), (double) (-blockposition_mutableblockposition.getY()), (double) (-blockposition_mutableblockposition.getZ())), d0); diff --git a/patches/server-unmapped/0001/0427-Don-t-tick-dead-players.patch b/patches/server-unmapped/0001/0427-Don-t-tick-dead-players.patch new file mode 100644 index 0000000000..d22fea4528 --- /dev/null +++ b/patches/server-unmapped/0001/0427-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/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index a37c868addc2e4aab8eb28d92b1c9b3e0c0fc4a9..e4647849e2fccb68ff9e88ced92c183204b76960 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -608,7 +608,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + public void playerTick() { + try { +- if (!this.isSpectator() || this.world.isLoaded(this.getChunkCoordinates())) { ++ if (valid && !this.isSpectator() || this.world.isLoaded(this.getChunkCoordinates())) { // Paper - don't tick dead players that are not in the world currently (pending respawn) + super.tick(); + } + diff --git a/patches/server-unmapped/0001/0428-Dead-Player-s-shouldn-t-be-able-to-move.patch b/patches/server-unmapped/0001/0428-Dead-Player-s-shouldn-t-be-able-to-move.patch new file mode 100644 index 0000000000..04592576bc --- /dev/null +++ b/patches/server-unmapped/0001/0428-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/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 564dfa98c166fde509044e6e1938efb321ece53d..8981dfacd10cae9de052e1b36ce5181cd0e6752d 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -1049,7 +1049,7 @@ public abstract class EntityHuman extends EntityLiving { + + @Override + protected boolean isFrozen() { +- return super.isFrozen() || this.isSleeping(); ++ return super.isFrozen() || this.isSleeping() || dead || !valid; // Paper - player's who are dead or not in a world shouldn't move... + } + + @Override diff --git a/patches/server-unmapped/0001/0429-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch b/patches/server-unmapped/0001/0429-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch new file mode 100644 index 0000000000..7a52148533 --- /dev/null +++ b/patches/server-unmapped/0001/0429-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch @@ -0,0 +1,294 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 8 Apr 2020 03:06:30 -0400 +Subject: [PATCH] Optimize PlayerChunkMap memory use for visibleChunks + +No longer clones visible chunks which is causing massive memory +allocation issues, likely the source of Humongous Objects on large servers. + +Instead we just synchronize, clear and rebuild, reusing the same object buffers +as before with only 2 small objects created (FastIterator/MapEntry) + +This should result in siginificant memory use reduction and improved GC behavior. + +diff --git a/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java b/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f6ff4d8132a95895680f5bc81f8f873e78f0bbdb +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java +@@ -0,0 +1,39 @@ ++package com.destroystokyo.paper.util.map; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; ++ ++public class Long2ObjectLinkedOpenHashMapFastCopy extends Long2ObjectLinkedOpenHashMap { ++ ++ public void copyFrom(Long2ObjectLinkedOpenHashMapFastCopy map) { ++ if (key.length != map.key.length) { ++ key = null; ++ key = new long[map.key.length]; ++ } ++ if (value.length != map.value.length) { ++ value = null; ++ //noinspection unchecked ++ value = (V[]) new Object[map.value.length]; ++ } ++ if (link.length != map.link.length) { ++ link = null; ++ link = new long[map.link.length]; ++ } ++ System.arraycopy(map.key, 0, this.key, 0, map.key.length); ++ System.arraycopy(map.value, 0, this.value, 0, map.value.length); ++ System.arraycopy(map.link, 0, this.link, 0, map.link.length); ++ this.size = map.size; ++ this.mask = map.mask; ++ this.first = map.first; ++ this.last = map.last; ++ this.n = map.n; ++ this.maxFill = map.maxFill; ++ this.containsNullKey = map.containsNullKey; ++ } ++ ++ @Override ++ public Long2ObjectLinkedOpenHashMapFastCopy clone() { ++ Long2ObjectLinkedOpenHashMapFastCopy clone = (Long2ObjectLinkedOpenHashMapFastCopy) super.clone(); ++ clone.copyFrom(this); ++ return clone; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 5a410550cfb48505c9de9979465ed1528c8fbf05..9edbde8299bcd127e1727d34ed441f638e716b2a 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -616,7 +616,7 @@ public final class MCUtil { + + WorldServer world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); + PlayerChunkMap chunkMap = world.getChunkProvider().playerChunkMap; +- Long2ObjectLinkedOpenHashMap visibleChunks = chunkMap.visibleChunks; ++ Long2ObjectLinkedOpenHashMap visibleChunks = chunkMap.getVisibleChunks(); + ChunkMapDistance chunkMapDistance = chunkMap.chunkDistanceManager; + List allChunks = new ArrayList<>(visibleChunks.values()); + List players = world.players; +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index db253b37a67f3e0f4b4dc32de35675ff8fae4d84..bbf42dba6b525f6a8e5569661c2935acacee0772 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -781,7 +781,7 @@ public class ChunkProviderServer extends IChunkProvider { + entityPlayer.playerNaturallySpawnedEvent.callEvent(); + }; + // Paper end +- this.playerChunkMap.f().forEach((playerchunk) -> { // Paper - no... just no... ++ this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping + Optional optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + + if (optional.isPresent()) { +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 12cfe9f3c89316557e94c8b944b4f82277fb8877..8050be2ed04fb0b8141f92595680407bba65dad5 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -106,8 +106,33 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + private static final Logger LOGGER = LogManager.getLogger(); + public static final int GOLDEN_TICKET = 33 + ChunkStatus.b(); +- public final Long2ObjectLinkedOpenHashMap updatingChunks = new Long2ObjectLinkedOpenHashMap(); +- public volatile Long2ObjectLinkedOpenHashMap visibleChunks; ++ // Paper start - faster copying ++ public final Long2ObjectLinkedOpenHashMap updatingChunks = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<>(); // Paper - faster copying ++ public final Long2ObjectLinkedOpenHashMap visibleChunks = new ProtectedVisibleChunksMap(); // Paper - faster copying ++ ++ private class ProtectedVisibleChunksMap extends com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy { ++ @Override ++ public PlayerChunk put(long k, PlayerChunk playerChunk) { ++ throw new UnsupportedOperationException("Updating visible Chunks"); ++ } ++ ++ @Override ++ public PlayerChunk remove(long k) { ++ throw new UnsupportedOperationException("Removing visible Chunks"); ++ } ++ ++ @Override ++ public PlayerChunk get(long k) { ++ return PlayerChunkMap.this.getVisibleChunk(k); ++ } ++ ++ public PlayerChunk safeGet(long k) { ++ return super.get(k); ++ } ++ } ++ // Paper end ++ public final com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy pendingVisibleChunks = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy(); // Paper - this is used if the visible chunks is updated while iterating only ++ public transient com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy visibleChunksClone; // Paper - used for async access of visible chunks, clone and cache only when needed + private final Long2ObjectLinkedOpenHashMap pendingUnload; + public final LongSet loadedChunks; // Paper - private -> public + public final WorldServer world; +@@ -180,7 +205,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + public PlayerChunkMap(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier supplier, int i, boolean flag) { + super(new File(convertable_conversionsession.a(worldserver.getDimensionKey()), "region"), datafixer, flag); +- this.visibleChunks = this.updatingChunks.clone(); ++ //this.visibleChunks = this.updatingChunks.clone(); // Paper - no more cloning + this.pendingUnload = new Long2ObjectLinkedOpenHashMap(); + this.loadedChunks = new LongOpenHashSet(); + this.unloadQueue = new LongOpenHashSet(); +@@ -272,9 +297,52 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return (PlayerChunk) this.updatingChunks.get(i); + } + ++ // Paper start - remove cloning of visible chunks unless accessed as a collection async ++ private static final boolean DEBUG_ASYNC_VISIBLE_CHUNKS = Boolean.getBoolean("paper.debug-async-visible-chunks"); ++ private boolean isIterating = false; ++ private boolean hasPendingVisibleUpdate = false; ++ public void forEachVisibleChunk(java.util.function.Consumer consumer) { ++ org.spigotmc.AsyncCatcher.catchOp("forEachVisibleChunk"); ++ boolean prev = isIterating; ++ isIterating = true; ++ try { ++ for (PlayerChunk value : this.visibleChunks.values()) { ++ consumer.accept(value); ++ } ++ } finally { ++ this.isIterating = prev; ++ if (!this.isIterating && this.hasPendingVisibleUpdate) { ++ ((ProtectedVisibleChunksMap)this.visibleChunks).copyFrom(this.pendingVisibleChunks); ++ this.pendingVisibleChunks.clear(); ++ this.hasPendingVisibleUpdate = false; ++ } ++ } ++ } ++ public Long2ObjectLinkedOpenHashMap getVisibleChunks() { ++ if (Thread.currentThread() == this.world.serverThread) { ++ return this.visibleChunks; ++ } else { ++ synchronized (this.visibleChunks) { ++ if (DEBUG_ASYNC_VISIBLE_CHUNKS) new Throwable("Async getVisibleChunks").printStackTrace(); ++ if (this.visibleChunksClone == null) { ++ this.visibleChunksClone = this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.clone() : ((ProtectedVisibleChunksMap)this.visibleChunks).clone(); ++ } ++ return this.visibleChunksClone; ++ } ++ } ++ } ++ // Paper end ++ + @Nullable + public PlayerChunk getVisibleChunk(long i) { // Paper - protected -> public +- return (PlayerChunk) this.visibleChunks.get(i); ++ // Paper start - mt safe get ++ if (Thread.currentThread() != this.world.serverThread) { ++ synchronized (this.visibleChunks) { ++ return (PlayerChunk) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(i) : ((ProtectedVisibleChunksMap)this.visibleChunks).safeGet(i)); ++ } ++ } ++ return (PlayerChunk) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(i) : ((ProtectedVisibleChunksMap)this.visibleChunks).safeGet(i)); ++ // Paper end + } + + protected IntSupplier c(long i) { +@@ -462,8 +530,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + // Paper end + + protected void save(boolean flag) { ++ Long2ObjectLinkedOpenHashMap visibleChunks = this.getVisibleChunks(); // Paper remove clone of visible Chunks unless saving off main thread (watchdog kill) + if (flag) { +- List list = (List) this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).peek(PlayerChunk::m).collect(Collectors.toList()); ++ List list = (List) visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).peek(PlayerChunk::m).collect(Collectors.toList()); // Paper - remove cloning of visible chunks + MutableBoolean mutableboolean = new MutableBoolean(); + + do { +@@ -491,7 +560,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + // this.i(); // Paper - nuke IOWorker + PlayerChunkMap.LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.w.getName()); + } else { +- this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).forEach((playerchunk) -> { ++ visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).forEach((playerchunk) -> { + IChunkAccess ichunkaccess = (IChunkAccess) playerchunk.getChunkSave().getNow(null); // CraftBukkit - decompile error + + if (ichunkaccess instanceof ProtoChunkExtension || ichunkaccess instanceof Chunk) { +@@ -662,7 +731,20 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + if (!this.updatingChunksModified) { + return false; + } else { +- this.visibleChunks = this.updatingChunks.clone(); ++ // Paper start - stop cloning visibleChunks ++ synchronized (this.visibleChunks) { ++ if (isIterating) { ++ hasPendingVisibleUpdate = true; ++ this.pendingVisibleChunks.copyFrom((com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy)this.updatingChunks); ++ } else { ++ hasPendingVisibleUpdate = false; ++ this.pendingVisibleChunks.clear(); ++ ((ProtectedVisibleChunksMap)this.visibleChunks).copyFrom((com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy)this.updatingChunks); ++ this.visibleChunksClone = null; ++ } ++ } ++ // Paper end ++ + this.updatingChunksModified = false; + return true; + } +@@ -1133,12 +1215,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + protected Iterable f() { +- return Iterables.unmodifiableIterable(this.visibleChunks.values()); ++ return Iterables.unmodifiableIterable(this.getVisibleChunks().values()); // Paper + } + + void a(Writer writer) throws IOException { + CSVWriter csvwriter = CSVWriter.a().a("x").a("z").a("level").a("in_memory").a("status").a("full_status").a("accessible_ready").a("ticking_ready").a("entity_ticking_ready").a("ticket").a("spawning").a("entity_count").a("block_entity_count").a(writer); +- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunks.long2ObjectEntrySet().iterator(); ++ ObjectBidirectionalIterator objectbidirectionaliterator = this.getVisibleChunks().long2ObjectEntrySet().iterator(); // Paper + + while (objectbidirectionaliterator.hasNext()) { + Entry entry = (Entry) objectbidirectionaliterator.next(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 5a7efa46729a233d3aa8b674b1f6a8043b6c5632..a6c1ef15784f7ae7bc703e5bc24cd2c97ad5b1a8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -294,6 +294,7 @@ public class CraftWorld implements World { + return ret; + } + public int getTileEntityCount() { ++ return net.minecraft.server.MCUtil.ensureMain(() -> { + // We don't use the full world tile entity list, so we must iterate chunks + Long2ObjectLinkedOpenHashMap chunks = world.getChunkProvider().playerChunkMap.visibleChunks; + int size = 0; +@@ -305,11 +306,13 @@ public class CraftWorld implements World { + size += chunk.tileEntities.size(); + } + return size; ++ }); + } + public int getTickableTileEntityCount() { + return world.tileEntityListTick.size(); + } + public int getChunkCount() { ++ return net.minecraft.server.MCUtil.ensureMain(() -> { + int ret = 0; + + for (PlayerChunk chunkHolder : world.getChunkProvider().playerChunkMap.visibleChunks.values()) { +@@ -318,7 +321,7 @@ public class CraftWorld implements World { + } + } + +- return ret; ++ return ret; }); + } + public int getPlayerCount() { + return world.players.size(); +@@ -443,6 +446,14 @@ public class CraftWorld implements World { + + @Override + public Chunk[] getLoadedChunks() { ++ // Paper start ++ if (Thread.currentThread() != world.getMinecraftWorld().serverThread) { ++ synchronized (world.getChunkProvider().playerChunkMap.visibleChunks) { ++ Long2ObjectLinkedOpenHashMap chunks = world.getChunkProvider().playerChunkMap.visibleChunks; ++ return chunks.values().stream().map(PlayerChunk::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.Chunk::getBukkitChunk).toArray(Chunk[]::new); ++ } ++ } ++ // Paper end + Long2ObjectLinkedOpenHashMap chunks = world.getChunkProvider().playerChunkMap.visibleChunks; + return chunks.values().stream().map(PlayerChunk::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.Chunk::getBukkitChunk).toArray(Chunk[]::new); + } diff --git a/patches/server-unmapped/0001/0430-Increase-Light-Queue-Size.patch b/patches/server-unmapped/0001/0430-Increase-Light-Queue-Size.patch new file mode 100644 index 0000000000..96e4c5ae70 --- /dev/null +++ b/patches/server-unmapped/0001/0430-Increase-Light-Queue-Size.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 8 Apr 2020 21:24:05 -0400 +Subject: [PATCH] Increase Light Queue Size + +Wiz mentioned that large WorldEdit operations cause light to run on +main thread. The queue was small, set to 5.. this bumps it to 20 +but makes it configurable per-world. + +The main risk of increasing this higher is during shutdown, some +queued light updates may be lost because mojang did not flush the +light engine on shutdown... + +The queue size only puts a cap on max loss, doesn't solve that problem. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 6c8e9d498c9a30a1aa88494ba09c3cae012a8fa1..cd248eb6be663e8be33f2c3c6b06b77b6d5753a4 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -620,4 +620,9 @@ public class PaperWorldConfig { + private void zombieVillagerInfectionChance() { + zombieVillagerInfectionChance = getDouble("zombie-villager-infection-chance", zombieVillagerInfectionChance); + } ++ ++ public int lightQueueSize = 20; ++ private void lightQueueSize() { ++ lightQueueSize = getInt("light-queue-size", lightQueueSize); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index dc817d7c7187de2b37485bef126fb0765a5caf63..4218dcb90c36bbac35ef292be32972e0fc22e6d2 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -776,7 +776,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Thu, 9 Apr 2020 00:09:26 -0400 +Subject: [PATCH] Mid Tick Chunk Tasks - Speed up processing of chunk loads and + generation + +Credit to Spotted for the idea + +A lot of the new chunk system requires constant back and forth the main thread +to handle priority scheduling and ensuring conflicting tasks do not run at the +same time. + +The issue is, these queues are only checked at either: + +A) Sync Chunk Loads +B) End of Tick while sleeping + +This results in generating chunks sitting waiting for a full tick to +complete before it will even start the next unit of work to do. + +Additionally, this also delays loading of chunks until this same timing. + +We will now periodically poll the chunk task queues throughout the tick, +looking for work to do. +We do this in a fair method that considers all worlds, not just the one being +ticked, so that each world can get 1 task procesed each before the next pass. + +In a view distance of 15, chunk loading performance was visually faster on the client. + +Flying at high speed in spectator mode was able to keep up with chunk loading (as long as they are already generated) + +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +index fed920e5ec65409377f181d74dcf9274d45aadc1..b4d43ceed368552e703886213327a0c0bb5ccb92 100644 +--- a/src/main/java/co/aikar/timings/MinecraftTimings.java ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -16,6 +16,7 @@ import java.util.Map; + public final class MinecraftTimings { + + public static final Timing serverOversleep = Timings.ofSafe("Server Oversleep"); ++ public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks"); + public static final Timing playerListTimer = Timings.ofSafe("Player List"); + public static final Timing commandFunctionsTimer = Timings.ofSafe("Command Functions"); + public static final Timing connectionTimer = Timings.ofSafe("Connection Handler"); +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index da93d38fe63035e4ff198ada84a4431f52d97c01..ddbc8cb712c50038922eded75dd6ca85fe851078 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -410,4 +410,9 @@ public class PaperConfig { + log("Async Chunks: Enabled - Chunks will be loaded much faster, without lag."); + } + } ++ ++ public static int midTickChunkTasks = 1000; ++ private static void midTickChunkTasks() { ++ midTickChunkTasks = getInt("settings.chunk-tasks-per-tick", midTickChunkTasks); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 4218dcb90c36bbac35ef292be32972e0fc22e6d2..18d078f85acf33e55e77758746f789af8f8d8076 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1056,6 +1056,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { ++ midTickLoadChunks(); // will only do loads since we are still considered !canSleepForTick + return !this.canOversleep(); + }); + isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); +@@ -1319,13 +1338,16 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { // Paper - safe iterator incase chunk loads, also no wrapping ++ final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping + Optional optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + + if (optional.isPresent()) { +@@ -805,6 +808,7 @@ public class ChunkProviderServer extends IChunkProvider { + this.world.timings.chunkTicks.startTiming(); // Spigot // Paper + this.world.a(chunk, k); + this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper ++ if (chunksTicked[0]++ % 10 == 0) this.world.getMinecraftServer().midTickLoadChunks(); // Paper + } + } + } +@@ -961,6 +965,41 @@ public class ChunkProviderServer extends IChunkProvider { + super.executeTask(runnable); + } + ++ // Paper start ++ private long lastMidTickChunkTask = 0; ++ public boolean pollChunkLoadTasks() { ++ if (com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask()) { ++ try { ++ ChunkProviderServer.this.tickDistanceManager(); ++ } finally { ++ // from below: process pending Chunk loadCallback() and unloadCallback() after each run task ++ playerChunkMap.callbackExecutor.run(); ++ } ++ return true; ++ } ++ return false; ++ } ++ public void midTickLoadChunks() { ++ MinecraftServer server = ChunkProviderServer.this.world.getMinecraftServer(); ++ // always try to load chunks, restrain generation/other updates only. don't count these towards tick count ++ //noinspection StatementWithEmptyBody ++ while (pollChunkLoadTasks()) {} ++ ++ if (System.nanoTime() - lastMidTickChunkTask < 200000) { ++ return; ++ } ++ ++ for (;server.midTickChunksTasksRan < com.destroystokyo.paper.PaperConfig.midTickChunkTasks && server.canSleepForTick();) { ++ if (this.executeNext()) { ++ server.midTickChunksTasksRan++; ++ lastMidTickChunkTask = System.nanoTime(); ++ } else { ++ break; ++ } ++ } ++ } ++ // Paper end ++ + @Override + protected boolean executeNext() { + // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 5d085321414134043e52d8012e12a8891529097c..c34ec6440b3c081a6e573b213f483c9ccf345c2b 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -571,6 +571,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + timings.scheduledBlocks.stopTiming(); // Paper + ++ this.getMinecraftServer().midTickLoadChunks(); // Paper + gameprofilerfiller.exitEnter("raid"); + this.timings.raids.startTiming(); // Paper - timings + this.persistentRaid.a(); +@@ -579,6 +580,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + timings.doSounds.startTiming(); // Spigot + this.ak(); + timings.doSounds.stopTiming(); // Spigot ++ this.getMinecraftServer().midTickLoadChunks(); // Paper + this.ticking = false; + gameprofilerfiller.exitEnter("entities"); + boolean flag3 = true || !this.players.isEmpty() || !this.getForceLoadedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players +@@ -645,6 +647,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + timings.entityTick.stopTiming(); // Spigot + + this.tickingEntities = false; ++ this.getMinecraftServer().midTickLoadChunks(); // Paper + + Entity entity2; + +@@ -654,6 +657,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + timings.tickEntities.stopTiming(); // Spigot ++ this.getMinecraftServer().midTickLoadChunks(); // Paper + this.tickBlockEntities(); + } + diff --git a/patches/server-unmapped/0001/0432-Don-t-move-existing-players-to-world-spawn.patch b/patches/server-unmapped/0001/0432-Don-t-move-existing-players-to-world-spawn.patch new file mode 100644 index 0000000000..8139947221 --- /dev/null +++ b/patches/server-unmapped/0001/0432-Don-t-move-existing-players-to-world-spawn.patch @@ -0,0 +1,54 @@ +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. + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index e4647849e2fccb68ff9e88ced92c183204b76960..3cf68af488fdd8492c620e6f3438b35cd4aa7737 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -253,7 +253,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.serverStatisticManager = minecraftserver.getPlayerList().getStatisticManager(this); + this.advancementDataPlayer = minecraftserver.getPlayerList().f(this); + this.G = 1.0F; +- this.c(worldserver); ++ //this.c(worldserver); // Paper - don't move to spawn on login, only first join + this.co = minecraftserver.a(this); + + this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper +@@ -305,6 +305,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + // CraftBukkit end + ++ public final void moveToSpawn(WorldServer worldserver) { c(worldserver); } // Paper - OBFHELPER + private void c(WorldServer worldserver) { + BlockPosition blockposition = worldserver.getSpawn(); + +@@ -482,7 +483,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + position = Vec3D.a(((WorldServer) world).getSpawn()); + } + this.world = world; +- this.setPosition(position.getX(), position.getY(), position.getZ()); ++ this.setPositionRaw(position.getX(), position.getY(), position.getZ()); // Paper - don't register to chunks yet + } + this.playerInteractManager.a((WorldServer) world); + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 84c2bc0bac4f388094693859ab7b6ced5e315c27..f35825d4a8574ea75b46be36b9929f8e12405217 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -208,6 +208,8 @@ public abstract class PlayerList { + worldserver1 = worldserver; + } + ++ if (nbttagcompound == null) entityplayer.moveToSpawn(worldserver1); // Paper - only move to spawn on first login, otherwise, stay where you are.... ++ + entityplayer.spawnIn(worldserver1); + entityplayer.playerInteractManager.a((WorldServer) entityplayer.world); + String s1 = "local"; diff --git a/patches/server-unmapped/0001/0433-Add-tick-times-API-and-mspt-command.patch b/patches/server-unmapped/0001/0433-Add-tick-times-API-and-mspt-command.patch new file mode 100644 index 0000000000..fa72e3c21b --- /dev/null +++ b/patches/server-unmapped/0001/0433-Add-tick-times-API-and-mspt-command.patch @@ -0,0 +1,169 @@ +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/com/destroystokyo/paper/MSPTCommand.java b/src/main/java/com/destroystokyo/paper/MSPTCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d0211d4f39f9d6af1d751ac66342b42cc6d7ba6d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/MSPTCommand.java +@@ -0,0 +1,64 @@ ++package com.destroystokyo.paper; ++ ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.ChatColor; ++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; ++ ++public class MSPTCommand extends Command { ++ private static final DecimalFormat DF = new DecimalFormat("########0.0"); ++ ++ public MSPTCommand(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("§6Server tick times §e(§7avg§e/§7min§e/§7max§e)§6 from last 5s§7,§6 10s§7,§6 1m§e:"); ++ sender.sendMessage(String.format("§6◴ %s§7/%s§7/%s§e, %s§7/%s§7/%s§e, %s§7/%s§7/%s", times.toArray())); ++ 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 String getColor(double avg) { ++ return ChatColor.COLOR_CHAR + (avg >= 50 ? "c" : avg >= 40 ? "e" : "a") + DF.format(avg); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index ddbc8cb712c50038922eded75dd6ca85fe851078..78271b400c79578d043b20a5389a37b1bef9a70d 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -69,6 +69,7 @@ public class PaperConfig { + + commands = new HashMap(); + commands.put("paper", new PaperCommand("paper")); ++ commands.put("mspt", new MSPTCommand("mspt")); + + version = getInt("config-version", 20); + set("config-version", 20); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 18d078f85acf33e55e77758746f789af8f8d8076..7ae852296900b0f2ca53311e6884f01e17e9980e 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -218,6 +218,11 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +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 f8e053a1c128b345051d00307997eb2bea79a98d..4828d356ca01cba5964c6397584d56643dbc0dae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2373,5 +2373,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-unmapped/0001/0435-Add-Raw-Byte-ItemStack-Serialization.patch b/patches/server-unmapped/0001/0435-Add-Raw-Byte-ItemStack-Serialization.patch new file mode 100644 index 0000000000..80b4197074 --- /dev/null +++ b/patches/server-unmapped/0001/0435-Add-Raw-Byte-ItemStack-Serialization.patch @@ -0,0 +1,102 @@ +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/net/minecraft/nbt/NBTCompressedStreamTools.java b/src/main/java/net/minecraft/nbt/NBTCompressedStreamTools.java +index 20410a5853e34c90c872f5e9592d50c4727e914d..860f084de38dc3f8723d881ff78fb1873f2b602a 100644 +--- a/src/main/java/net/minecraft/nbt/NBTCompressedStreamTools.java ++++ b/src/main/java/net/minecraft/nbt/NBTCompressedStreamTools.java +@@ -51,6 +51,7 @@ public class NBTCompressedStreamTools { + return nbttagcompound; + } + ++ public static NBTTagCompound readNBT(InputStream inputstream) throws IOException { return a(inputstream); } // Paper - OBFHELPER + public static NBTTagCompound a(InputStream inputstream) throws IOException { + DataInputStream datainputstream = new DataInputStream(new BufferedInputStream(new GZIPInputStream(inputstream))); + Throwable throwable = null; +@@ -106,6 +107,7 @@ public class NBTCompressedStreamTools { + + } + ++ public static void writeNBT(NBTTagCompound nbttagcompound, OutputStream outputstream) throws IOException { a(nbttagcompound, outputstream); } // Paper - OBFHELPER + public static void a(NBTTagCompound nbttagcompound, OutputStream outputstream) throws IOException { + DataOutputStream dataoutputstream = new DataOutputStream(new BufferedOutputStream(new GZIPOutputStream(outputstream))); + Throwable throwable = null; +diff --git a/src/main/java/net/minecraft/util/datafix/DataConverterRegistry.java b/src/main/java/net/minecraft/util/datafix/DataConverterRegistry.java +index 6527509e6aed7187667c681af682e9a02937a224..28e36ee76da533f8aa0a09cfc4f1fc0f744af715 100644 +--- a/src/main/java/net/minecraft/util/datafix/DataConverterRegistry.java ++++ b/src/main/java/net/minecraft/util/datafix/DataConverterRegistry.java +@@ -208,6 +208,7 @@ public class DataConverterRegistry { + return datafixerbuilder.build(SystemUtils.e()); + } + ++ public static DataFixer getDataFixer() { return a(); } // Paper - OBFHELPER + public static DataFixer a() { + return DataConverterRegistry.c; + } +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 661f400ae4f5cebef5d1743819529ecf647b6681..0468f80b7f52ee45fc9364470b23f80f7cd0cb57 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -198,6 +198,7 @@ public final class ItemStack { + this.checkEmpty(); + } + ++ public static ItemStack fromCompound(NBTTagCompound nbttagcompound) { return a(nbttagcompound); } // Paper - OBFHELPER + public static ItemStack a(NBTTagCompound nbttagcompound) { + try { + return new ItemStack(nbttagcompound); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 7e4cceff7ce9ffaff00caf21088fd7bc59e66933..2519dbce9717ff647d50c31aed6aca68b9f4e3af 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -380,6 +380,46 @@ public final class CraftMagicNumbers implements UnsafeValues { + public boolean isSupportedApiVersion(String apiVersion) { + return apiVersion != null && SUPPORTED_API.contains(apiVersion); + } ++ ++ @Override ++ public byte[] serializeItem(ItemStack item) { ++ Preconditions.checkNotNull(item, "null cannot be serialized"); ++ Preconditions.checkArgument(item.getType() != Material.AIR, "air cannot be serialized"); ++ ++ java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); ++ NBTTagCompound compound = (item instanceof CraftItemStack ? ((CraftItemStack) item).getHandle() : CraftItemStack.asNMSCopy(item)).save(new NBTTagCompound()); ++ compound.setInt("DataVersion", getDataVersion()); ++ try { ++ net.minecraft.nbt.NBTCompressedStreamTools.writeNBT( ++ compound, ++ outputStream ++ ); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ return outputStream.toByteArray(); ++ } ++ ++ @Override ++ public ItemStack deserializeItem(byte[] data) { ++ Preconditions.checkNotNull(data, "null cannot be deserialized"); ++ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); ++ ++ try { ++ NBTTagCompound compound = net.minecraft.nbt.NBTCompressedStreamTools.readNBT( ++ new java.io.ByteArrayInputStream(data) ++ ); ++ int dataVersion = compound.getInt("DataVersion"); ++ ++ Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); ++ Dynamic converted = DataConverterRegistry.getDataFixer().update(DataConverterTypes.ITEM_STACK, new Dynamic(DynamicOpsNBT.a, compound), dataVersion, getDataVersion()); ++ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.fromCompound((NBTTagCompound) converted.getValue())); ++ } catch (IOException ex) { ++ com.destroystokyo.paper.util.SneakyThrow.sneaky(ex); ++ throw new RuntimeException(); ++ } ++ } + // Paper end + + /** diff --git a/patches/server-unmapped/0001/0436-Remove-streams-from-Mob-AI-System.patch b/patches/server-unmapped/0001/0436-Remove-streams-from-Mob-AI-System.patch new file mode 100644 index 0000000000..4937798620 --- /dev/null +++ b/patches/server-unmapped/0001/0436-Remove-streams-from-Mob-AI-System.patch @@ -0,0 +1,253 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 17:53:29 -0700 +Subject: [PATCH] Remove streams from Mob AI System + +The streams hurt performance and allocate tons of garbage, so +replace them with the standard iterator. + +Also optimise the stream.anyMatch statement to move to a bitset +where we can replace the call with a single bitwise operation. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java +index b505c23c57a4b84faf5906c6295455b4720c4426..5c32cbe81c47fcb9ae347faa6fc007c5d28d79bf 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java +@@ -1,10 +1,12 @@ + package net.minecraft.world.entity.ai.goal; + ++import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; // Paper - remove streams from pathfindergoalselector + import java.util.EnumSet; + + public abstract class PathfinderGoal { + +- private final EnumSet a = EnumSet.noneOf(PathfinderGoal.Type.class); ++ private final EnumSet a = EnumSet.noneOf(PathfinderGoal.Type.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be. ++ private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Paper - remove streams from pathfindergoalselector + + public PathfinderGoal() {} + +@@ -28,16 +30,20 @@ public abstract class PathfinderGoal { + public void e() {} + + public void a(EnumSet enumset) { +- this.a.clear(); +- this.a.addAll(enumset); ++ // Paper start - remove streams from pathfindergoalselector ++ this.goalTypes.clear(); ++ this.goalTypes.addAllUnchecked(enumset); ++ // Paper end - remove streams from pathfindergoalselector + } + + public String toString() { + return this.getClass().getSimpleName(); + } + +- public EnumSet i() { +- return this.a; ++ // Paper start - remove streams from pathfindergoalselector ++ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet getGoalTypes() { ++ return this.goalTypes; ++ // Paper end - remove streams from pathfindergoalselector + } + + public static enum Type { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java +index 8c234c09a4d9ada83e36e3cdbcc1f2f5c6202f28..385cd079e264a7e66e91ab3b70b90afb59688dcd 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java +@@ -1,8 +1,10 @@ + package net.minecraft.world.entity.ai.goal; + ++import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; // Paper - remove streams from pathfindergoalselector + import com.google.common.collect.Sets; + import java.util.EnumMap; + import java.util.EnumSet; ++import java.util.Iterator; // Paper - remove streams from pathfindergoalselector + import java.util.Map; + import java.util.Set; + import java.util.function.Supplier; +@@ -28,7 +30,8 @@ public class PathfinderGoalSelector { + private final Map c = new EnumMap(PathfinderGoal.Type.class); + private final Set d = Sets.newLinkedHashSet(); private Set getTasks() { return d; }// Paper - OBFHELPER + private final Supplier e; +- private final EnumSet f = EnumSet.noneOf(PathfinderGoal.Type.class); ++ private final EnumSet f = EnumSet.noneOf(PathfinderGoal.Type.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be. ++ private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Paper - remove streams from pathfindergoalselector + private int g = 3;private int getTickRate() { return g; } // Paper - OBFHELPER + private int curRate;private int getCurRate() { return curRate; } private void incRate() { this.curRate++; } // Paper TODO + +@@ -56,35 +59,38 @@ public class PathfinderGoalSelector { + // Paper end + + public void a(PathfinderGoal pathfindergoal) { +- this.d.stream().filter((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.j() == pathfindergoal; +- }).filter(PathfinderGoalWrapped::g).forEach(PathfinderGoalWrapped::d); +- this.d.removeIf((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.j() == pathfindergoal; +- }); ++ // Paper start - remove streams from pathfindergoalselector ++ for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { ++ PathfinderGoalWrapped goalWrapped = iterator.next(); ++ if (goalWrapped.j() != pathfindergoal) { ++ continue; ++ } ++ if (goalWrapped.g()) { ++ goalWrapped.d(); ++ } ++ iterator.remove(); ++ } ++ // Paper end - remove streams from pathfindergoalselector + } + ++ private static final PathfinderGoal.Type[] PATHFINDER_GOAL_TYPES = PathfinderGoal.Type.values(); // Paper - remove streams from pathfindergoalselector ++ + public void doTick() { + GameProfilerFiller gameprofilerfiller = (GameProfilerFiller) this.e.get(); + + gameprofilerfiller.enter("goalCleanup"); +- this.d().filter((pathfindergoalwrapped) -> { +- boolean flag; +- +- if (pathfindergoalwrapped.g()) { +- Stream stream = pathfindergoalwrapped.i().stream(); +- EnumSet enumset = this.f; +- +- this.f.getClass(); +- if (!stream.anyMatch(enumset::contains) && pathfindergoalwrapped.b()) { +- flag = false; +- return flag; +- } ++ // Paper start - remove streams from pathfindergoalselector ++ for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { ++ PathfinderGoalWrapped wrappedGoal = iterator.next(); ++ if (!wrappedGoal.g()) { ++ continue; + } +- +- flag = true; +- return flag; +- }).forEach(PathfinderGoal::d); ++ if (!this.goalTypes.hasCommonElements(wrappedGoal.getGoalTypes()) && wrappedGoal.b()) { ++ continue; ++ } ++ wrappedGoal.d(); ++ } ++ // Paper end - remove streams from pathfindergoalselector + this.c.forEach((pathfindergoal_type, pathfindergoalwrapped) -> { + if (!pathfindergoalwrapped.g()) { + this.c.remove(pathfindergoal_type); +@@ -93,30 +99,58 @@ public class PathfinderGoalSelector { + }); + gameprofilerfiller.exit(); + gameprofilerfiller.enter("goalUpdate"); +- this.d.stream().filter((pathfindergoalwrapped) -> { +- return !pathfindergoalwrapped.g(); +- }).filter((pathfindergoalwrapped) -> { +- Stream stream = pathfindergoalwrapped.i().stream(); +- EnumSet enumset = this.f; +- +- this.f.getClass(); +- return stream.noneMatch(enumset::contains); +- }).filter((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.i().stream().allMatch((pathfindergoal_type) -> { +- return ((PathfinderGoalWrapped) this.c.getOrDefault(pathfindergoal_type, PathfinderGoalSelector.b)).a(pathfindergoalwrapped); +- }); +- }).filter(PathfinderGoalWrapped::a).forEach((pathfindergoalwrapped) -> { +- pathfindergoalwrapped.i().forEach((pathfindergoal_type) -> { +- PathfinderGoalWrapped pathfindergoalwrapped1 = (PathfinderGoalWrapped) this.c.getOrDefault(pathfindergoal_type, PathfinderGoalSelector.b); +- +- pathfindergoalwrapped1.d(); +- this.c.put(pathfindergoal_type, pathfindergoalwrapped); +- }); +- pathfindergoalwrapped.c(); +- }); ++ // Paper start - remove streams from pathfindergoalselector ++ goal_update_loop: for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { ++ PathfinderGoalWrapped wrappedGoal = iterator.next(); ++ if (wrappedGoal.g()) { ++ continue; ++ } ++ ++ OptimizedSmallEnumSet wrappedGoalSet = wrappedGoal.getGoalTypes(); ++ ++ if (this.goalTypes.hasCommonElements(wrappedGoalSet)) { ++ continue; ++ } ++ ++ long iterator1 = wrappedGoalSet.getBackingSet(); ++ int wrappedGoalSize = wrappedGoalSet.size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ PathfinderGoal.Type type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)]; ++ iterator1 ^= com.destroystokyo.paper.util.math.IntegerUtil.getTrailingBit(iterator1); ++ PathfinderGoalWrapped wrapped = this.c.getOrDefault(type, PathfinderGoalSelector.b); ++ if (!wrapped.a(wrappedGoal)) { ++ continue goal_update_loop; ++ } ++ } ++ ++ if (!wrappedGoal.a()) { ++ continue; ++ } ++ ++ iterator1 = wrappedGoalSet.getBackingSet(); ++ wrappedGoalSize = wrappedGoalSet.size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ PathfinderGoal.Type type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)]; ++ iterator1 ^= com.destroystokyo.paper.util.math.IntegerUtil.getTrailingBit(iterator1); ++ PathfinderGoalWrapped wrapped = this.c.getOrDefault(type, PathfinderGoalSelector.b); ++ ++ wrapped.d(); ++ this.c.put(type, wrappedGoal); ++ } ++ ++ wrappedGoal.c(); ++ } ++ // Paper end - remove streams from pathfindergoalselector + gameprofilerfiller.exit(); + gameprofilerfiller.enter("goalTick"); +- this.d().forEach(PathfinderGoalWrapped::e); ++ // Paper start - remove streams from pathfindergoalselector ++ for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { ++ PathfinderGoalWrapped wrappedGoal = iterator.next(); ++ if (wrappedGoal.g()) { ++ wrappedGoal.e(); ++ } ++ } ++ // Paper end - remove streams from pathfindergoalselector + gameprofilerfiller.exit(); + } + +@@ -125,11 +159,11 @@ public class PathfinderGoalSelector { + } + + public void a(PathfinderGoal.Type pathfindergoal_type) { +- this.f.add(pathfindergoal_type); ++ this.goalTypes.addUnchecked(pathfindergoal_type); // Paper - remove streams from pathfindergoalselector + } + + public void b(PathfinderGoal.Type pathfindergoal_type) { +- this.f.remove(pathfindergoal_type); ++ this.goalTypes.removeUnchecked(pathfindergoal_type); // Paper - remove streams from pathfindergoalselector + } + + public void a(PathfinderGoal.Type pathfindergoal_type, boolean flag) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java +index 7bb531e47668cf445083c4dedb03ccafe6a9c96b..8c8e39d35fb56aa6cf7d456adab01dff5d13a60d 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java +@@ -59,9 +59,10 @@ public class PathfinderGoalWrapped extends PathfinderGoal { + this.a.a(enumset); + } + +- @Override +- public EnumSet i() { +- return this.a.i(); ++ // Paper start - remove streams from pathfindergoalselector ++ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet getGoalTypes() { ++ return this.a.getGoalTypes(); ++ // Paper end - remove streams from pathfindergoalselector + } + + public boolean isRunning() { return this.g(); } // Paper - OBFHELPER diff --git a/patches/server-unmapped/0001/0437-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch b/patches/server-unmapped/0001/0437-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch new file mode 100644 index 0000000000..ace543ae0c --- /dev/null +++ b/patches/server-unmapped/0001/0437-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 11 Apr 2020 21:23:42 -0400 +Subject: [PATCH] Delay unsafe actions until after entity ticking is done + +This will help prevent many cases of unregistering entities during entity ticking + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index c34ec6440b3c081a6e573b213f483c9ccf345c2b..ae99f3fb3a4b37c737fb276590004b2e10beab5a 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -178,6 +178,16 @@ public class WorldServer extends World implements GeneratorAccessSeed { + public final List players = Lists.newArrayList(); // Paper - private -> public + public final ChunkProviderServer chunkProvider; // Paper - public + boolean tickingEntities; ++ // Paper start ++ List afterEntityTickingTasks = Lists.newArrayList(); ++ public void doIfNotEntityTicking(java.lang.Runnable run) { ++ if (tickingEntities) { ++ afterEntityTickingTasks.add(run); ++ } else { ++ run.run(); ++ } ++ } ++ // Paper end + private final MinecraftServer server; + public final WorldDataServer worldDataServer; // CraftBukkit - type + public boolean savingDisabled; +@@ -647,6 +657,16 @@ public class WorldServer extends World implements GeneratorAccessSeed { + timings.entityTick.stopTiming(); // Spigot + + this.tickingEntities = false; ++ // Paper start ++ for (java.lang.Runnable run : this.afterEntityTickingTasks) { ++ try { ++ run.run(); ++ } catch (Exception e) { ++ LOGGER.error("Error in After Entity Ticking Task", e); ++ } ++ } ++ this.afterEntityTickingTasks.clear(); ++ // Paper end + this.getMinecraftServer().midTickLoadChunks(); // Paper + + Entity entity2; diff --git a/patches/server-unmapped/0001/0438-Async-command-map-building.patch b/patches/server-unmapped/0001/0438-Async-command-map-building.patch new file mode 100644 index 0000000000..463fac0fc7 --- /dev/null +++ b/patches/server-unmapped/0001/0438-Async-command-map-building.patch @@ -0,0 +1,48 @@ +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 + + +diff --git a/src/main/java/net/minecraft/commands/CommandDispatcher.java b/src/main/java/net/minecraft/commands/CommandDispatcher.java +index ddbd4c43bbe6d6a5b90f4958c4be80520a40beab..c97424b401147be53ffa7e2a2a3271d696752efe 100644 +--- a/src/main/java/net/minecraft/commands/CommandDispatcher.java ++++ b/src/main/java/net/minecraft/commands/CommandDispatcher.java +@@ -29,6 +29,7 @@ import net.minecraft.network.chat.ChatHoverable; + import net.minecraft.network.chat.ChatMessage; + import net.minecraft.network.chat.IChatMutableComponent; + import net.minecraft.network.protocol.game.PacketPlayOutCommands; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.commands.CommandAdvancement; + import net.minecraft.server.commands.CommandAttribute; + import net.minecraft.server.commands.CommandBan; +@@ -327,6 +328,14 @@ public class CommandDispatcher { + if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot + // CraftBukkit start + // Register Vanilla commands into builtRoot as before ++ // Paper start - Async command map building ++ java.util.concurrent.ForkJoinPool.commonPool().execute(() -> { ++ sendAsync(entityplayer); ++ }); ++ } ++ ++ private void sendAsync(EntityPlayer entityplayer) { ++ // Paper end - Async command map building + Map, CommandNode> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues + RootCommandNode vanillaRoot = new RootCommandNode(); + +@@ -344,7 +353,14 @@ public class CommandDispatcher { + for (CommandNode node : rootcommandnode.getChildren()) { + bukkit.add(node.getName()); + } ++ // Paper start - Async command map building ++ MinecraftServer.getServer().execute(() -> { ++ runSync(entityplayer, bukkit, rootcommandnode); ++ }); ++ } + ++ private void runSync(EntityPlayer entityplayer, Collection bukkit, RootCommandNode rootcommandnode) { ++ // Paper end - Async command map building + PlayerCommandSendEvent event = new PlayerCommandSendEvent(entityplayer.getBukkitEntity(), new LinkedHashSet<>(bukkit)); + event.getPlayer().getServer().getPluginManager().callEvent(event); + diff --git a/patches/server-unmapped/0001/0439-Improved-Watchdog-Support.patch b/patches/server-unmapped/0001/0439-Improved-Watchdog-Support.patch new file mode 100644 index 0000000000..9373ce23c4 --- /dev/null +++ b/patches/server-unmapped/0001/0439-Improved-Watchdog-Support.patch @@ -0,0 +1,604 @@ +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 0b9e689d57705965721b5c55bc45d36657f360e4..dee00aac05f1acf050f05d4db557a08dd0f301c8 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 d0fdb9ce57b22a1f582cddec9afcc35b75d58cc6..9b7a51890c667601b195ff15b2bf0d6c76c7f19f 100644 +--- a/src/main/java/net/minecraft/CrashReport.java ++++ b/src/main/java/net/minecraft/CrashReport.java +@@ -257,6 +257,7 @@ public class CrashReport { + } + + public static CrashReport a(Throwable throwable, String s) { ++ if (throwable instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(throwable); // Paper + while (throwable instanceof CompletionException && throwable.getCause() != null) { + throwable = throwable.getCause(); + } +diff --git a/src/main/java/net/minecraft/SystemUtils.java b/src/main/java/net/minecraft/SystemUtils.java +index 397194b3e90c9df39cfae17b401c7ac891b0dbb7..61b4c42e95994343772a91640b243b8e8224e09b 100644 +--- a/src/main/java/net/minecraft/SystemUtils.java ++++ b/src/main/java/net/minecraft/SystemUtils.java +@@ -130,6 +130,7 @@ public class SystemUtils { + return SystemUtils.f; + } + ++ public static void shutdownServerThreadPool() { h(); } // Paper - OBFHELPER + public static void h() { + a(SystemUtils.e); + a(SystemUtils.f); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 7ae852296900b0f2ca53311e6884f01e17e9980e..5c7ded8244187d993d8dda4afff95d9cfc45579b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -270,7 +270,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant S a(Function function) { + AtomicReference atomicreference = new AtomicReference(); + Thread thread = new Thread(() -> { +@@ -852,6 +854,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { ++ world.tickingEntities = false; ++ }); ++ } ++ // Paper end + // CraftBukkit end + MinecraftServer.LOGGER.info("Stopping server"); + MinecraftTimings.stopServer(); // Paper +@@ -931,7 +951,18 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant {}; ++ } ++ // Paper end + return new TickTask(this.ticks, runnable); + } + +@@ -1422,6 +1478,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { + CompletableFuture completablefuture; +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index ae99f3fb3a4b37c737fb276590004b2e10beab5a..06fa9b91cc103a5d5f39ab8fcfb5ccad4cf0e5de 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -177,7 +177,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + private final Queue entitiesToAdd = Queues.newArrayDeque(); + public final List players = Lists.newArrayList(); // Paper - private -> public + public final ChunkProviderServer chunkProvider; // Paper - public +- boolean tickingEntities; ++ public boolean tickingEntities; // Paper - expose for watchdog + // Paper start + List afterEntityTickingTasks = Lists.newArrayList(); + public void doIfNotEntityTicking(java.lang.Runnable run) { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index f35825d4a8574ea75b46be36b9929f8e12405217..48d4f8310d79d0eb78f4ace42c8778ee4addf7d0 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -507,7 +507,7 @@ public abstract class PlayerList { + cserver.getPluginManager().callEvent(playerQuitEvent); + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + +- entityplayer.playerTick(); // SPIGOT-924 ++ if (server.isMainThread()) entityplayer.playerTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog) + // CraftBukkit end + + // Paper start - Remove from collideRule team if needed +diff --git a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +index ca23ca14d8011fc8daa7e20f2eaa550a8ff92c53..158ea6d77698d62ba795aff6c061a80652e42e03 100644 +--- a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java ++++ b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +@@ -135,6 +135,7 @@ public abstract class IAsyncTaskHandler implements Mailbox 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 449e99d1b673870ed6892f6ab2c715a2db35c35d..c7ed6e0f8a989cec97700df2b15198c9c481c549 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +@@ -12,12 +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 + org.spigotmc.AsyncCatcher.shuttingDown = true; // Paper ++ server.forceTicks = true; + server.close(); ++ while (!server.hasFullyShutdown) Thread.sleep(1000); ++ } catch (InterruptedException e) { ++ e.printStackTrace(); ++ // Paper end + } finally { + 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 b45d7e5c108c7a8541fcbc9ad92d1a79a94746a1..6a408dc9286a60c3ca7830f88171919fb0fe6363 100644 +--- a/src/main/java/org/spigotmc/RestartCommand.java ++++ b/src/main/java/org/spigotmc/RestartCommand.java +@@ -139,7 +139,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 58e50bf0fb0f309227e1f4c1f6bb11c01d8e08d3..30a665c090f419985e1d0f49df9e8d110c83943a 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -13,6 +13,7 @@ import org.bukkit.Bukkit; + public class WatchdogThread extends Thread + { + ++ public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper + private static WatchdogThread instance; + private long timeoutTime; + private boolean restart; +@@ -41,6 +42,7 @@ public class WatchdogThread extends Thread + { + if ( instance == null ) + { ++ if (timeoutTime <= 0) timeoutTime = 300; // Paper + instance = new WatchdogThread( timeoutTime * 1000L, restart ); + instance.start(); + } else +@@ -71,12 +73,13 @@ public class WatchdogThread extends Thread + // Paper start + Logger log = Bukkit.getServer().getLogger(); + long currentTime = monotonicMillis(); +- if ( lastTick != 0 && timeoutTime > 0 && currentTime > lastTick + earlyWarningEvery && !Boolean.getBoolean("disable.watchdog") ) ++ MinecraftServer server = MinecraftServer.getServer(); ++ if (lastTick != 0 && timeoutTime > 0 && hasStarted && (!server.isRunning() || (currentTime > lastTick + earlyWarningEvery && !DISABLE_WATCHDOG) )) + { +- 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 +@@ -118,7 +121,7 @@ public class WatchdogThread extends Thread + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper +- dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); ++ dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // + // Paper start - Only print full dump on long timeouts +@@ -139,9 +142,24 @@ public class WatchdogThread extends Thread + + if ( isLongTimeout ) + { +- if ( restart && !MinecraftServer.getServer().hasStopped() ) ++ if ( !server.hasStopped() ) + { +- RestartCommand.restart(); ++ AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us ++ AsyncCatcher.shuttingDown = true; ++ server.forceTicks = true; ++ if (restart) { ++ RestartCommand.addShutdownHook( SpigotConfig.restartScript ); ++ } ++ // try one last chance to safe shutdown on main incase it 'comes back' ++ 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 476f4a5cbe664ddd05474cb88553018bd334a5b8..8af159abd3d0cc94cf155fec5b384c42f69551bf 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -1,5 +1,5 @@ + +- ++ + + + diff --git a/patches/server-unmapped/0001/0440-Optimize-Pathfinding.patch b/patches/server-unmapped/0001/0440-Optimize-Pathfinding.patch new file mode 100644 index 0000000000..812fed5f72 --- /dev/null +++ b/patches/server-unmapped/0001/0440-Optimize-Pathfinding.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 02:02:07 -0600 +Subject: [PATCH] Optimize Pathfinding + +Prevents pathfinding from spamming failures for things such as +arrow attacks. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +index 06d05b511d623d0247d44989bee85b383a8fb52f..e6ba9b7fbf08ae0dd083a1ebee8eb7ed8a937751 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java +@@ -11,6 +11,7 @@ import net.minecraft.core.BlockPosition; + import net.minecraft.core.IPosition; + import net.minecraft.network.protocol.game.PacketDebug; + import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.util.MathHelper; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityInsentient; +@@ -32,7 +33,7 @@ public abstract class NavigationAbstract { + protected final EntityInsentient a; public Entity getEntity() { return a; } // Paper - OBFHELPER + protected final World b; + @Nullable +- protected PathEntity c; ++ protected PathEntity c; protected final PathEntity getCurrentPath() { return this.c; } // Paper - OBFHELPER + protected double d; + protected int e; + protected int f; +@@ -184,10 +185,30 @@ public abstract class NavigationAbstract { + return this.a(this.a(d0, d1, d2, 1), d3); + } + ++ // Paper start - optimise pathfinding ++ private int lastFailure = 0; ++ private int pathfindFailures = 0; ++ // Paper end ++ + public boolean a(Entity entity, double d0) { ++ // Paper start - Pathfinding optimizations ++ if (this.pathfindFailures > 10 && this.getCurrentPath() == null && MinecraftServer.currentTick < this.lastFailure + 40) { ++ return false; ++ } ++ // Paper end + PathEntity pathentity = this.a(entity, 1); + +- return pathentity != null && this.a(pathentity, d0); ++ // Paper start - Pathfinding optimizations ++ if (pathentity != null && this.a(pathentity, d0)) { ++ this.lastFailure = 0; ++ this.pathfindFailures = 0; ++ return true; ++ } else { ++ this.pathfindFailures++; ++ this.lastFailure = MinecraftServer.currentTick; ++ return false; ++ } ++ // Paper end + } + + public boolean setDestination(@Nullable PathEntity pathentity, double speed) { return a(pathentity, speed); } // Paper - OBFHELPER diff --git a/patches/server-unmapped/0001/0441-Reduce-Either-Optional-allocation.patch b/patches/server-unmapped/0001/0441-Reduce-Either-Optional-allocation.patch new file mode 100644 index 0000000000..0f4641263b --- /dev/null +++ b/patches/server-unmapped/0001/0441-Reduce-Either-Optional-allocation.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 18:35:09 -0700 +Subject: [PATCH] Reduce Either Optional allocation + +In order to get chunk values, we shouldn't need to create +an optional each time. + +diff --git a/src/main/java/com/mojang/datafixers/util/Either.java b/src/main/java/com/mojang/datafixers/util/Either.java +index a90adac7bd7ebd423f480e9ae0f44cb9d521fa4f..3f65fe71024928e35111fc6719a290aab9a6859e 100644 +--- a/src/main/java/com/mojang/datafixers/util/Either.java ++++ b/src/main/java/com/mojang/datafixers/util/Either.java +@@ -22,7 +22,7 @@ public abstract class Either implements App, L> { + } + + private static final class Left extends Either { +- private final L value; ++ private final L value; private Optional valueOptional; // Paper - reduce the optional allocation... + + public Left(final L value) { + this.value = value; +@@ -51,7 +51,7 @@ public abstract class Either implements App, L> { + + @Override + public Optional left() { +- return Optional.of(value); ++ return this.valueOptional == null ? this.valueOptional = Optional.of(this.value) : this.valueOptional; // Paper - reduce the optional allocation... + } + + @Override +@@ -83,7 +83,7 @@ public abstract class Either implements App, L> { + } + + private static final class Right extends Either { +- private final R value; ++ private final R value; private Optional valueOptional; // Paper - reduce the optional allocation... + + public Right(final R value) { + this.value = value; +@@ -117,7 +117,7 @@ public abstract class Either implements App, L> { + + @Override + public Optional right() { +- return Optional.of(value); ++ return this.valueOptional == null ? this.valueOptional = Optional.of(this.value) : this.valueOptional; // Paper - reduce the optional allocation... + } + + @Override diff --git a/patches/server-unmapped/0001/0442-Remove-streams-from-PairedQueue.patch b/patches/server-unmapped/0001/0442-Remove-streams-from-PairedQueue.patch new file mode 100644 index 0000000000..f1a9a0128a --- /dev/null +++ b/patches/server-unmapped/0001/0442-Remove-streams-from-PairedQueue.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 18:10:43 -0700 +Subject: [PATCH] Remove streams from PairedQueue + +We shouldn't be doing stream calls just to see if the queue is +empty. This creates loads of garbage thanks to how often it's called. + +diff --git a/src/main/java/net/minecraft/util/thread/PairedQueue.java b/src/main/java/net/minecraft/util/thread/PairedQueue.java +index 024b414aae32c8ad32bdf031361257fc74b80eb3..56c9efa78ceeac499182eb401e605a60dad12182 100644 +--- a/src/main/java/net/minecraft/util/thread/PairedQueue.java ++++ b/src/main/java/net/minecraft/util/thread/PairedQueue.java +@@ -20,32 +20,30 @@ public interface PairedQueue { + + public static final class a implements PairedQueue { + +- private final List> a; ++ private final List> a; private final List> getQueues() { return this.a; } // Paper - OBFHELPER + + public a(int i) { +- this.a = (List) IntStream.range(0, i).mapToObj((j) -> { +- return Queues.newConcurrentLinkedQueue(); +- }).collect(Collectors.toList()); ++ // Paper start - remove streams ++ this.a = new java.util.ArrayList<>(i); // queues ++ for (int j = 0; j < i; ++j) { ++ this.getQueues().add(Queues.newConcurrentLinkedQueue()); ++ } ++ // Paper end - remove streams + } + + @Nullable + @Override + public Runnable a() { +- Iterator iterator = this.a.iterator(); +- +- Runnable runnable; +- +- do { +- if (!iterator.hasNext()) { +- return null; ++ // Paper start - remove iterator creation ++ for (int i = 0, len = this.getQueues().size(); i < len; ++i) { ++ Queue queue = this.getQueues().get(i); ++ Runnable ret = queue.poll(); ++ if (ret != null) { ++ return ret; + } +- +- Queue queue = (Queue) iterator.next(); +- +- runnable = (Runnable) queue.poll(); +- } while (runnable == null); +- +- return runnable; ++ } ++ return null; ++ // Paper end - remove iterator creation + } + + public boolean a(PairedQueue.b pairedqueue_b) { +@@ -57,7 +55,16 @@ public interface PairedQueue { + + @Override + public boolean b() { +- return this.a.stream().allMatch(Collection::isEmpty); ++ // Paper start - remove streams ++ // why are we doing streams every time we might want to execute a task? ++ for (int i = 0, len = this.getQueues().size(); i < len; ++i) { ++ Queue queue = this.getQueues().get(i); ++ if (!queue.isEmpty()) { ++ return false; ++ } ++ } ++ return true; ++ // Paper end - remove streams + } + } + diff --git a/patches/server-unmapped/0001/0443-Reduce-memory-footprint-of-NBTTagCompound.patch b/patches/server-unmapped/0001/0443-Reduce-memory-footprint-of-NBTTagCompound.patch new file mode 100644 index 0000000000..a8f074ccde --- /dev/null +++ b/patches/server-unmapped/0001/0443-Reduce-memory-footprint-of-NBTTagCompound.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 17:39:25 -0700 +Subject: [PATCH] Reduce memory footprint of NBTTagCompound + +Fastutil maps are going to have a lower memory footprint - which +is important because we clone chunk data after reading it for safety. +So, reduce the impact of the clone on GC. + +diff --git a/src/main/java/net/minecraft/nbt/NBTTagCompound.java b/src/main/java/net/minecraft/nbt/NBTTagCompound.java +index d5508deff819309034554abc7b36aac40fa33503..77afbaad5b2cb8d912f5404fcbd3a0970490f4f3 100644 +--- a/src/main/java/net/minecraft/nbt/NBTTagCompound.java ++++ b/src/main/java/net/minecraft/nbt/NBTTagCompound.java +@@ -26,6 +26,7 @@ import net.minecraft.ReportedException; + import net.minecraft.network.chat.ChatComponentText; + import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.network.chat.IChatMutableComponent; ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; // Paper + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + +@@ -47,7 +48,7 @@ public class NBTTagCompound implements NBTBase { + if (i > 512) { + throw new RuntimeException("Tried to read NBT tag with too high complexity, depth > 512"); + } else { +- HashMap hashmap = Maps.newHashMap(); ++ Object2ObjectOpenHashMap hashmap = new Object2ObjectOpenHashMap<>(8, 0.8f); // Paper - reduce memory footprint of NBTTagCompound + + byte b0; + +@@ -83,7 +84,7 @@ public class NBTTagCompound implements NBTBase { + } + + public NBTTagCompound() { +- this(Maps.newHashMap()); ++ this(new Object2ObjectOpenHashMap<>(8, 0.8f)); // Paper - reduce memory footprint of NBTTagCompound + } + + @Override +@@ -417,9 +418,17 @@ public class NBTTagCompound implements NBTBase { + + @Override + public NBTTagCompound clone() { +- Map map = Maps.newHashMap(Maps.transformValues(this.map, NBTBase::clone)); ++ // Paper start - reduce memory footprint of NBTTagCompound ++ Object2ObjectOpenHashMap ret = new Object2ObjectOpenHashMap<>(this.map.size(), 0.8f); + +- return new NBTTagCompound(map); ++ Iterator> iterator = (this.map instanceof Object2ObjectOpenHashMap) ? ((Object2ObjectOpenHashMap)this.map).object2ObjectEntrySet().fastIterator() : this.map.entrySet().iterator(); ++ while (iterator.hasNext()) { ++ Map.Entry entry = iterator.next(); ++ ret.put(entry.getKey(), entry.getValue().clone()); ++ } ++ ++ return new NBTTagCompound(ret); ++ // Paper end - reduce memory footprint of NBTTagCompound + } + + public boolean equals(Object object) { diff --git a/patches/server-unmapped/0001/0444-Prevent-opening-inventories-when-frozen.patch b/patches/server-unmapped/0001/0444-Prevent-opening-inventories-when-frozen.patch new file mode 100644 index 0000000000..e226e409f6 --- /dev/null +++ b/patches/server-unmapped/0001/0444-Prevent-opening-inventories-when-frozen.patch @@ -0,0 +1,59 @@ +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/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 3cf68af488fdd8492c620e6f3438b35cd4aa7737..3e23bda4c1f379d28b722d21d600627a61a65ff0 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -559,7 +559,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + containerUpdateDelay = world.paperConfig.containerUpdateTickRate; + } + // Paper end +- if (!this.world.isClientSide && !this.activeContainer.canUse(this)) { ++ if (!this.world.isClientSide && this.activeContainer != this.defaultContainer && (isFrozen() || !this.activeContainer.canUse(this))) { // Paper - auto close while frozen + this.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper + this.activeContainer = this.defaultContainer; + } +@@ -1398,7 +1398,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } else { + // CraftBukkit start + this.activeContainer = container; +- this.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, container.getType(), container.getTitle())); ++ if (!isFrozen()) this.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, container.getType(), container.getTitle())); // Paper + // CraftBukkit end + container.addSlotListener(this); + return OptionalInt.of(this.containerCounter); +@@ -2200,7 +2200,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + + @Override +- protected boolean isFrozen() { ++ public boolean isFrozen() { // Paper - protected > public + return super.isFrozen() || (this.playerConnection != null && this.playerConnection.isDisconnected()); // Paper + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index e8f8a07f256e01c5792199bf47f3cc1f0f3d1610..5b142e96248278c6bb6068879bb5ad1578b0f79f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -322,7 +322,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + if (adventure$title == null) adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(container.getBukkitView().getTitle()); // Paper + + //player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); // Paper // Paper - comment +- player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper ++ if (!player.isFrozen()) player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper + getHandle().activeContainer = container; + getHandle().activeContainer.addSlotListener(player); + } +@@ -396,7 +396,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper + if (adventure$title == null) adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(inventory.getTitle()); // Paper + //player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment +- player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper ++ if (!player.isFrozen()) player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper + player.activeContainer = container; + player.activeContainer.addSlotListener(player); + } diff --git a/patches/server-unmapped/0001/0445-Optimise-ArraySetSorted-removeIf.patch b/patches/server-unmapped/0001/0445-Optimise-ArraySetSorted-removeIf.patch new file mode 100644 index 0000000000..1ff7b9b2f9 --- /dev/null +++ b/patches/server-unmapped/0001/0445-Optimise-ArraySetSorted-removeIf.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 15 Apr 2020 18:23:28 -0700 +Subject: [PATCH] Optimise ArraySetSorted#removeIf + +Remove iterator allocation and ensure the call is always O(n) + +diff --git a/src/main/java/net/minecraft/util/ArraySetSorted.java b/src/main/java/net/minecraft/util/ArraySetSorted.java +index 427daa94322f47b4eaf881d85a01fed239db549a..936a90272579f78832eff93f2a81d673feb669c1 100644 +--- a/src/main/java/net/minecraft/util/ArraySetSorted.java ++++ b/src/main/java/net/minecraft/util/ArraySetSorted.java +@@ -10,8 +10,8 @@ import java.util.NoSuchElementException; + public class ArraySetSorted extends AbstractSet { + + private final Comparator a; +- private T[] b; +- private int c; ++ private T[] b; private final T[] getBackingArray() { return this.b; } // Paper - OBFHELPER ++ private int c; private final int getSize() { return this.c; } private final void setSize(int value) { this.c = value; } // Paper - OBFHELPER + + private ArraySetSorted(int i, Comparator comparator) { + this.a = comparator; +@@ -22,6 +22,42 @@ public class ArraySetSorted extends AbstractSet { + } + } + ++ // 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.getSize(); ++ T[] backingArray = this.getBackingArray(); ++ ++ 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.setSize(lastIndex); ++ return true; ++ } ++ // Paper end - optimise removeIf ++ + public static > ArraySetSorted a(int i) { + return new ArraySetSorted<>(i, (Comparator)Comparator.naturalOrder()); // Paper - decompile fix + } diff --git a/patches/server-unmapped/0001/0446-Don-t-run-entity-collision-code-if-not-needed.patch b/patches/server-unmapped/0001/0446-Don-t-run-entity-collision-code-if-not-needed.patch new file mode 100644 index 0000000000..da7931a459 --- /dev/null +++ b/patches/server-unmapped/0001/0446-Don-t-run-entity-collision-code-if-not-needed.patch @@ -0,0 +1,30 @@ +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 craming is disabled and +the max collisions per entity is less than or equal to 0 + +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 86c6a8fd4511dfe426cc1651d289f38b467d3029..dc9e12c38d1682f6c4558ca07b781de2226c0621 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -2944,10 +2944,16 @@ public abstract class EntityLiving extends Entity { + protected void doTick() {} + + protected void collideNearby() { ++ // Paper - start don't run getEntities if we're not going to use its result ++ int i = this.world.getGameRules().getInt(GameRules.MAX_ENTITY_CRAMMING); ++ if (i <= 0 && world.paperConfig.maxCollisionsPerEntity <= 0) { ++ return; ++ } ++ // Paper - end don't run getEntities if we're not going to use its result + List list = this.world.getEntities(this, this.getBoundingBox(), IEntitySelector.a(this)); + + if (!list.isEmpty()) { +- int i = this.world.getGameRules().getInt(GameRules.MAX_ENTITY_CRAMMING); ++ // Paper - move up + int j; + + if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) { diff --git a/patches/server-unmapped/0001/0447-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch b/patches/server-unmapped/0001/0447-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch new file mode 100644 index 0000000000..ced8156723 --- /dev/null +++ b/patches/server-unmapped/0001/0447-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 16 Apr 2020 16:13:59 -0700 +Subject: [PATCH] Optimize ChunkProviderServer's chunk level checking helper + methods + +These can be hot functions (i.e entity ticking and block ticking), +so inline where possible, and avoid the abstraction of the +Either class. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index 002a6da6ce3c1a800af8f0f11090871b36adffcc..8e9398e98f5ea64c3db5a317de21bddc1a82f268 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -644,21 +644,29 @@ public class ChunkProviderServer extends IChunkProvider { + + public final boolean isInEntityTickingChunk(Entity entity) { return this.a(entity); } // Paper - OBFHELPER + @Override public boolean a(Entity entity) { +- long i = ChunkCoordIntPair.pair(MathHelper.floor(entity.locX()) >> 4, MathHelper.floor(entity.locZ()) >> 4); +- +- return this.a(i, (Function>>) PlayerChunk::b); // CraftBukkit - decompile error ++ // Paper start - optimize is ticking ready type functions ++ // entity ticking ++ PlayerChunk playerChunk = this.getChunk(MCUtil.getCoordinateKey(entity)); ++ return playerChunk != null && playerChunk.isEntityTickingReady(); ++ // Paper end - optimize is ticking ready type functions + } + + public final boolean isEntityTickingChunk(ChunkCoordIntPair chunkcoordintpair) { return this.a(chunkcoordintpair); } // Paper - OBFHELPER + @Override public boolean a(ChunkCoordIntPair chunkcoordintpair) { +- return this.a(chunkcoordintpair.pair(), (Function>>) PlayerChunk::b); // CraftBukkit - decompile error ++ // Paper start - optimize is ticking ready type functions ++ // is entity ticking ready ++ PlayerChunk playerChunk = this.getChunk(MCUtil.getCoordinateKey(chunkcoordintpair)); ++ return playerChunk != null && playerChunk.isEntityTickingReady(); ++ // Paper end - optimize is ticking ready type functions + } + + @Override + public boolean a(BlockPosition blockposition) { +- long i = ChunkCoordIntPair.pair(blockposition.getX() >> 4, blockposition.getZ() >> 4); +- +- return this.a(i, (Function>>) PlayerChunk::a); // CraftBukkit - decompile error ++ // Paper start - optimize is ticking ready type functions ++ // is ticking ready ++ PlayerChunk playerChunk = this.getChunk(MCUtil.getCoordinateKey(blockposition)); ++ return playerChunk != null && playerChunk.isTickingReady(); ++ // Paper end - optimize is ticking ready type functions + } + + private boolean a(long i, Function>> function) { diff --git a/patches/server-unmapped/0001/0448-Restrict-vanilla-teleport-command-to-valid-locations.patch b/patches/server-unmapped/0001/0448-Restrict-vanilla-teleport-command-to-valid-locations.patch new file mode 100644 index 0000000000..ed7ce57a14 --- /dev/null +++ b/patches/server-unmapped/0001/0448-Restrict-vanilla-teleport-command-to-valid-locations.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 16 Apr 2020 20:07:29 -0500 +Subject: [PATCH] Restrict vanilla teleport command to valid locations + +Fixes GH-3165, GH-3575 + +diff --git a/src/main/java/net/minecraft/server/commands/CommandTeleport.java b/src/main/java/net/minecraft/server/commands/CommandTeleport.java +index b59763643def065cd998fa3005523c37973613fd..dcc92b0c9a7a0214a3e803d714c5e7078c17a039 100644 +--- a/src/main/java/net/minecraft/server/commands/CommandTeleport.java ++++ b/src/main/java/net/minecraft/server/commands/CommandTeleport.java +@@ -142,6 +142,12 @@ public class CommandTeleport { + + private static void a(CommandListenerWrapper commandlistenerwrapper, Entity entity, WorldServer worldserver, double d0, double d1, double d2, Set set, float f, float f1, @Nullable CommandTeleport.a commandteleport_a) throws CommandSyntaxException { + BlockPosition blockposition = new BlockPosition(d0, d1, d2); ++ // Paper start - Don't allow teleport command to invalid locations ++ if (d0 <= -30000000 || d2 <= -30000000 || d0 > 30000000 || d2 > 30000000 || d1 > 30000000 || d1 <= -30000000) { // Copy/pasta from BaseBlockPosition#isValidLocation ++ org.bukkit.Bukkit.getLogger().warning("Refused to teleport " + entity.getName() + " to " + d0 + ", " + d1 + ", " + d2); ++ return; ++ } ++ // Paper end + + if (!World.l(blockposition)) { + throw CommandTeleport.a.create(); diff --git a/patches/server-unmapped/0001/0449-Implement-Player-Client-Options-API.patch b/patches/server-unmapped/0001/0449-Implement-Player-Client-Options-API.patch new file mode 100644 index 0000000000..1af31ef629 --- /dev/null +++ b/patches/server-unmapped/0001/0449-Implement-Player-Client-Options-API.patch @@ -0,0 +1,188 @@ +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 + + +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/network/protocol/game/PacketPlayInSettings.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInSettings.java +index 90842b27f64afcdd8eb7d0e52df8cfcb418b5b5a..f47cd43f96f61475bd1d5da11bdbc7c5e5f7e1bc 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInSettings.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInSettings.java +@@ -41,14 +41,17 @@ public class PacketPlayInSettings implements Packet { + packetlistenerplayin.a(this); + } + ++ public EnumChatVisibility getChatVisibility() { return d(); } // Paper - OBFHELPER + public EnumChatVisibility d() { + return this.c; + } + ++ public boolean hasChatColorsEnabled() { return e(); } // Paper - OBFHELPER + public boolean e() { + return this.d; + } + ++ public int getSkinParts() { return f(); } // Paper - OBFHELPER + public int f() { + return this.e; + } +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 3e23bda4c1f379d28b722d21d600627a61a65ff0..d010aed07a1e608897ca5f87afcb7661e295d933 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -2,6 +2,7 @@ package net.minecraft.server.level; + + import com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent; + import com.google.common.collect.Lists; ++import com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent; // Paper + import com.mojang.authlib.GameProfile; + import com.mojang.datafixers.util.Either; + import com.mojang.serialization.DataResult; +@@ -190,7 +191,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public int lastSentExp = -99999999; + public int invulnerableTicks = 60; + private EnumChatVisibility bY; +- private boolean bZ = true; ++ private boolean bZ = true; public boolean hasChatColorsEnabled() { return this.bZ; } // Paper - OBFHELPER + private long ca = SystemUtils.getMonotonicMillis(); + private Entity spectatedEntity; + public boolean worldChangeInvuln; +@@ -1801,6 +1802,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public String locale = null; // CraftBukkit - add, lowercase // Paper - default to null + public java.util.Locale adventure$locale = java.util.Locale.US; // Paper + public void a(PacketPlayInSettings packetplayinsettings) { ++ new PlayerClientOptionsChangeEvent(getBukkitEntity(), packetplayinsettings.locale, packetplayinsettings.viewDistance, com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(packetplayinsettings.getChatVisibility().name()), packetplayinsettings.hasChatColorsEnabled(), new com.destroystokyo.paper.PaperSkinParts(packetplayinsettings.getSkinParts()), packetplayinsettings.getMainHand() == EnumMainHand.LEFT ? MainHand.LEFT : MainHand.RIGHT).callEvent(); // Paper - settings event + // CraftBukkit start + if (getMainHand() != packetplayinsettings.getMainHand()) { + PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(getBukkitEntity(), getMainHand() == EnumMainHand.LEFT ? MainHand.LEFT : MainHand.RIGHT); +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 8981dfacd10cae9de052e1b36ce5181cd0e6752d..202fa94d5dc55b549475ae0309bbcfca8f1b2c96 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -133,7 +133,7 @@ public abstract class EntityHuman extends EntityLiving { + private static final Map b = ImmutableMap.builder().put(EntityPose.STANDING, EntityHuman.bh).put(EntityPose.SLEEPING, EntityHuman.ah).put(EntityPose.FALL_FLYING, EntitySize.b(0.6F, 0.6F)).put(EntityPose.SWIMMING, EntitySize.b(0.6F, 0.6F)).put(EntityPose.SPIN_ATTACK, EntitySize.b(0.6F, 0.6F)).put(EntityPose.CROUCHING, EntitySize.b(0.6F, 1.5F)).put(EntityPose.DYING, EntitySize.c(0.2F, 0.2F)).build(); + private static final DataWatcherObject c = DataWatcher.a(EntityHuman.class, DataWatcherRegistry.c); + private static final DataWatcherObject d = DataWatcher.a(EntityHuman.class, DataWatcherRegistry.b); +- protected static final DataWatcherObject bi = DataWatcher.a(EntityHuman.class, DataWatcherRegistry.a); ++ protected static final DataWatcherObject bi = DataWatcher.a(EntityHuman.class, DataWatcherRegistry.a); public static DataWatcherObject getSkinPartsWatcher() { return bi; } // Paper - OBFHELPER + protected static final DataWatcherObject bj = DataWatcher.a(EntityHuman.class, DataWatcherRegistry.a); + protected static final DataWatcherObject bk = DataWatcher.a(EntityHuman.class, DataWatcherRegistry.p); + protected static final DataWatcherObject bl = DataWatcher.a(EntityHuman.class, DataWatcherRegistry.p); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 43e4ade73619d430be7ee93687e98ef5a27cb329..7a240739ac6e043e590b380659d8e6d794954f84 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1,5 +1,8 @@ + package org.bukkit.craftbukkit.entity; + ++import com.destroystokyo.paper.ClientOption.ChatVisibility; ++import com.destroystokyo.paper.PaperSkinParts; ++import com.destroystokyo.paper.ClientOption; + import com.destroystokyo.paper.Title; + import com.google.common.base.Preconditions; + import com.google.common.collect.ImmutableSet; +@@ -2250,6 +2253,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + public void setViewDistance(int viewDistance) { + throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO + } ++ ++ @Override ++ public T getClientOption(ClientOption type) { ++ if(ClientOption.SKIN_PARTS.equals(type)) { ++ return type.getType().cast(new PaperSkinParts(getHandle().getDataWatcher().get(EntityHuman.getSkinPartsWatcher()))); ++ } else if(ClientOption.CHAT_COLORS_ENABLED.equals(type)) { ++ return type.getType().cast(getHandle().hasChatColorsEnabled()); ++ } else if(ClientOption.CHAT_VISIBILITY.equals(type)) { ++ return type.getType().cast(getHandle().getChatFlags() == null ? ChatVisibility.UNKNOWN : ChatVisibility.valueOf(getHandle().getChatFlags().name())); ++ } else if(ClientOption.LOCALE.equals(type)) { ++ return type.getType().cast(getLocale()); ++ } else if(ClientOption.MAIN_HAND.equals(type)) { ++ return type.getType().cast(getMainHand()); ++ } else if(ClientOption.VIEW_DISTANCE.equals(type)) { ++ return type.getType().cast(getClientViewDistance()); ++ } ++ throw new RuntimeException("Unknown settings type"); ++ } + // Paper end + + // Spigot start diff --git a/patches/server-unmapped/0001/0450-Fix-Chunk-Post-Processing-deadlock-risk.patch b/patches/server-unmapped/0001/0450-Fix-Chunk-Post-Processing-deadlock-risk.patch new file mode 100644 index 0000000000..0c5e1b86e8 --- /dev/null +++ b/patches/server-unmapped/0001/0450-Fix-Chunk-Post-Processing-deadlock-risk.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 18 Apr 2020 04:36:11 -0400 +Subject: [PATCH] Fix Chunk Post Processing deadlock risk + +See: https://gist.github.com/aikar/dd22bbd2a3d78a2fd3d92e95e9f28dc6 + +as part of post processing a chunk, we can call ChunkConverter. + +ChunkConverter then kicks off major physics updates, and when blocks +that have connections across chunk boundries occur, a recursive risk +can occur where A updates a block that triggers a physics request. + +That physics request may trigger a chunk request, that then enqueues +a task into the Mailbox ChunkTaskQueueSorter. + +If anything requests that same chunk that is in the middle of conversion, +it's mailbox queue is going to be held up, so the subsequent chunk request +will be unable to proceed. + +We delay post processing of Chunk.A() 1 "pass" by re stuffing it back into +the executor so that the mailbox ChunkQueue is now considered empty. + +This successfully fixed a reoccurring and highly reproduceable crash +for heightmaps. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index 8e9398e98f5ea64c3db5a317de21bddc1a82f268..f5197ec91743a91848ce9406fe10ec89a5ab5126 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -1020,6 +1020,7 @@ public class ChunkProviderServer extends IChunkProvider { + return super.executeNext() || execChunkTask; // Paper + } + } finally { ++ playerChunkMap.chunkLoadConversionCallbackExecutor.run(); // Paper - Add chunk load conversion callback executor to prevent deadlock due to recursion in the chunk task queue sorter + playerChunkMap.callbackExecutor.run(); + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index bb9c6e9aeb1f30af01338476ba1dd618b14124d5..80c7ff059b78f55ec9c390bd728186a94074e603 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -183,6 +183,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + }; + // CraftBukkit end + ++ final CallbackExecutor chunkLoadConversionCallbackExecutor = new CallbackExecutor(); // Paper ++ + // Paper start - distance maps + private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); + +@@ -1048,7 +1050,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return Either.left(chunk); + }); + }, (runnable) -> { +- this.mailboxMain.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); ++ this.mailboxMain.a(ChunkTaskQueueSorter.a(playerchunk, () -> PlayerChunkMap.this.chunkLoadConversionCallbackExecutor.execute(runnable))); // Paper - delay running Chunk post processing until outside of the sorter to prevent a deadlock scenario when post processing causes another chunk request. + }); + + completablefuture1.thenAcceptAsync((either) -> { diff --git a/patches/server-unmapped/0001/0451-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch b/patches/server-unmapped/0001/0451-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch new file mode 100644 index 0000000000..83e1af8a9c --- /dev/null +++ b/patches/server-unmapped/0001/0451-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/ChunkMapDistance.java b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +index 961257ebc28a8b4753faf3c2d5b6abaea4ffc0dd..60fcea78bf617559114b1ca1c0bf2d4cd9075a8c 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +@@ -245,8 +245,8 @@ public abstract class ChunkMapDistance { + ObjectSet objectset = (ObjectSet) this.c.get(i); + if (objectset == null) return; // CraftBukkit - SPIGOT-6208 + +- objectset.remove(entityplayer); +- if (objectset.isEmpty()) { ++ if (objectset != null) objectset.remove(entityplayer); // Paper - some state corruption happens here, don't crash, clean up gracefully. ++ if (objectset == null || objectset.isEmpty()) { // Paper + this.c.remove(i); + this.f.update(i, Integer.MAX_VALUE, false); + this.g.update(i, Integer.MAX_VALUE, false); diff --git a/patches/server-unmapped/0001/0452-Broadcast-join-message-to-console.patch b/patches/server-unmapped/0001/0452-Broadcast-join-message-to-console.patch new file mode 100644 index 0000000000..680595c2bd --- /dev/null +++ b/patches/server-unmapped/0001/0452-Broadcast-join-message-to-console.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AvrooVulcan +Date: Fri, 17 Apr 2020 00:15:23 +0100 +Subject: [PATCH] Broadcast join message to console + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 48d4f8310d79d0eb78f4ace42c8778ee4addf7d0..599470c2158222be9202a96fc7d9bcf4b0ca0fe1 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -288,7 +288,9 @@ public abstract class PlayerList { + + if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure + joinMessage = PaperAdventure.asVanilla(jm); // Paper - Adventure +- server.getPlayerList().sendAll(new PacketPlayOutChat(joinMessage, ChatMessageType.SYSTEM, SystemUtils.b)); // Paper - Adventure ++ // Paper start - Removed sendAll for loop and broadcasted to console also ++ server.getPlayerList().sendMessage(joinMessage); // Paper - Adventure ++ // Paper end + } + // CraftBukkit end + diff --git a/patches/server-unmapped/0001/0453-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch b/patches/server-unmapped/0001/0453-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch new file mode 100644 index 0000000000..9b911a6475 --- /dev/null +++ b/patches/server-unmapped/0001/0453-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch @@ -0,0 +1,111 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 19 Apr 2020 00:05:46 -0400 +Subject: [PATCH] Fix Longstanding Broken behavior of PlayerJoinEvent + +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. + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index d010aed07a1e608897ca5f87afcb7661e295d933..abd2554b6e98c25b59b9989936af8b0624e255a3 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -239,6 +239,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public double maxHealthCache; + public boolean joining = true; + public boolean sentListPacket = false; ++ public boolean supressTrackerForLogin = false; // Paper + public Integer clientViewDistance; + // CraftBukkit end + public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 80c7ff059b78f55ec9c390bd728186a94074e603..94860c06717e8dcf969277562e88687e9a99aaa4 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -1578,7 +1578,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + }); + } + +- protected void addEntity(Entity entity) { ++ public void addEntity(Entity entity) { // Paper - protected -> public + org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot + // Paper start - ignore and warn about illegal addEntity calls instead of crashing server + if (!entity.valid || entity.world != this.world || this.trackedEntities.containsKey(entity.getId())) { +@@ -1587,6 +1587,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + .printStackTrace(); + return; + } ++ if (entity instanceof EntityPlayer && ((EntityPlayer) entity).supressTrackerForLogin) return; // Delay adding to tracker until after list packets + // Paper end + if (!(entity instanceof EntityComplexPart)) { + EntityTypes entitytypes = entity.getEntityType(); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 599470c2158222be9202a96fc7d9bcf4b0ca0fe1..7305bba223229d9aa5315a8dff5fdc5af8faba6a 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -276,6 +276,12 @@ public abstract class PlayerList { + this.j.put(entityplayer.getUniqueID(), entityplayer); + // this.sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, new EntityPlayer[]{entityplayer})); // CraftBukkit - replaced with loop below + ++ // Paper start - correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks ++ entityplayer.supressTrackerForLogin = true; ++ worldserver1.addPlayerJoin(entityplayer); ++ this.server.getBossBattleCustomData().a(entityplayer); // see commented out section below worldserver.addPlayerJoin(entityplayer); ++ mountSavedVehicle(entityplayer, worldserver1, nbttagcompound); ++ // Paper end + // CraftBukkit start + PlayerJoinEvent playerJoinEvent = new org.bukkit.event.player.PlayerJoinEvent(cserver.getPlayer(entityplayer), PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure + cserver.getPluginManager().callEvent(playerJoinEvent); +@@ -311,6 +317,8 @@ public abstract class PlayerList { + entityplayer.playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, new EntityPlayer[] { entityplayer1})); + } + entityplayer.sentListPacket = true; ++ entityplayer.supressTrackerForLogin = false; // Paper ++ ((WorldServer)entityplayer.world).getChunkProvider().playerChunkMap.addEntity(entityplayer); // Paper - track entity now + // CraftBukkit end + + entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityMetadata(entityplayer.getId(), entityplayer.getDataWatcher(), true)); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn +@@ -336,6 +344,11 @@ public abstract class PlayerList { + playerconnection.sendPacket(new PacketPlayOutEntityEffect(entityplayer.getId(), mobeffect)); + } + ++ // Paper start - move vehicle into method so it can be called above - short circuit around that code ++ onPlayerJoinFinish(entityplayer, worldserver1, s1); ++ } ++ private void mountSavedVehicle(EntityPlayer entityplayer, WorldServer worldserver1, NBTTagCompound nbttagcompound) { ++ // Paper end + if (nbttagcompound != null && nbttagcompound.hasKeyOfType("RootVehicle", 10)) { + NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("RootVehicle"); + // CraftBukkit start +@@ -384,6 +397,10 @@ public abstract class PlayerList { + } + } + ++ // Paper start ++ } ++ public void onPlayerJoinFinish(EntityPlayer entityplayer, WorldServer worldserver1, String s1) { ++ // Paper end + entityplayer.syncInventory(); + // Paper start - Add to collideRule team if needed + final Scoreboard scoreboard = this.getServer().getWorldServer(World.OVERWORLD).getScoreboard(); diff --git a/patches/server-unmapped/0001/0454-Load-Chunks-for-Login-Asynchronously.patch b/patches/server-unmapped/0001/0454-Load-Chunks-for-Login-Asynchronously.patch new file mode 100644 index 0000000000..285b4ef2b9 --- /dev/null +++ b/patches/server-unmapped/0001/0454-Load-Chunks-for-Login-Asynchronously.patch @@ -0,0 +1,306 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 19 Apr 2020 04:28:29 -0400 +Subject: [PATCH] Load Chunks for Login Asynchronously + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index f5197ec91743a91848ce9406fe10ec89a5ab5126..e45457b793ca5e087f976c212020cf6a28183f5e 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -630,7 +630,7 @@ public class ChunkProviderServer extends IChunkProvider { + return this.serverThreadQueue.executeNext(); + } + +- private boolean tickDistanceManager() { ++ public boolean tickDistanceManager() { // Paper - private -> public + boolean flag = this.chunkMapDistance.a(this.playerChunkMap); + boolean flag1 = this.playerChunkMap.b(); + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index abd2554b6e98c25b59b9989936af8b0624e255a3..56a4de74d665b5dba97340bb6d9d35ec112c11f9 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -31,6 +31,7 @@ import net.minecraft.core.NonNullList; + import net.minecraft.core.SectionPosition; + import net.minecraft.nbt.DynamicOpsNBT; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.network.NetworkManager; + import net.minecraft.network.chat.ChatComponentText; + import net.minecraft.network.chat.ChatHoverable; + import net.minecraft.network.chat.ChatMessage; +@@ -174,6 +175,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + private static final Logger LOGGER = LogManager.getLogger(); + public PlayerConnection playerConnection; ++ public NetworkManager networkManager; // Paper + public final MinecraftServer server; + public final PlayerInteractManager playerInteractManager; + public final Deque removeQueue = new ArrayDeque<>(); // Paper +@@ -240,6 +242,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public boolean joining = true; + public boolean sentListPacket = false; + public boolean supressTrackerForLogin = false; // Paper ++ public boolean didPlayerJoinEvent = false; // Paper + public Integer clientViewDistance; + // CraftBukkit end + public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 94860c06717e8dcf969277562e88687e9a99aaa4..e5d94dedc88e8bbcf8d9517dfb5c6ec6f62de5aa 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -147,7 +147,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + private final Mailbox> mailboxWorldGen; + private final Mailbox> mailboxMain; + public final WorldLoadListener worldLoadListener; +- public final PlayerChunkMap.a chunkDistanceManager; ++ public final PlayerChunkMap.a chunkDistanceManager; public final ChunkMapDistance getChunkDistanceManager() { return this.chunkDistanceManager; } // Paper - OBFHELPER + private final AtomicInteger u; + public final DefinedStructureManager definedStructureManager; // Paper - private -> public + private final File w; +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 218dc900e125a11548485887b1918742072c7a77..2c932d36f982e7f8713aabff9a6c631055810366 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -21,6 +21,7 @@ public class TicketType { + public static final TicketType FORCED = a("forced", Comparator.comparingLong(ChunkCoordIntPair::pair)); + public static final TicketType LIGHT = a("light", Comparator.comparingLong(ChunkCoordIntPair::pair)); + public static final TicketType PORTAL = a("portal", BaseBlockPosition::compareTo, 300); ++ public static final TicketType LOGIN = a("login", Long::compareTo, 100); // Paper + public static final TicketType POST_TELEPORT = a("post_teleport", Integer::compareTo, 5); + public static final TicketType UNKNOWN = a("unknown", Comparator.comparingLong(ChunkCoordIntPair::pair), 1); + public static final TicketType PLUGIN = a("plugin", (a, b) -> 0); // CraftBukkit +diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java +index 4dd2f7fb32b8618d752e0988acadcb41223c0e4c..78a8eb89e9113a1002ba6177f96d5734a10e8d7d 100644 +--- a/src/main/java/net/minecraft/server/network/LoginListener.java ++++ b/src/main/java/net/minecraft/server/network/LoginListener.java +@@ -88,7 +88,7 @@ public class LoginListener implements PacketLoginInListener { + } + // Paper end + } else if (this.g == LoginListener.EnumProtocolState.DELAY_ACCEPT) { +- EntityPlayer entityplayer = this.server.getPlayerList().getPlayer(this.i.getId()); ++ EntityPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.i.getId()); // Paper + + if (entityplayer == null) { + this.g = LoginListener.EnumProtocolState.READY_TO_ACCEPT; +@@ -187,7 +187,7 @@ public class LoginListener implements PacketLoginInListener { + } + + this.networkManager.sendPacket(new PacketLoginOutSuccess(this.i)); +- EntityPlayer entityplayer = this.server.getPlayerList().getPlayer(this.i.getId()); ++ EntityPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.i.getId()); // Paper + + if (entityplayer != null) { + this.g = LoginListener.EnumProtocolState.DELAY_ACCEPT; +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index f02ddd53df4674a2b5e0bb142db756d1f153d69b..443247b03b8352c4dd453270dccdbd7eb5f0944b 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -222,6 +222,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + private static final Logger LOGGER = LogManager.getLogger(); + public final NetworkManager networkManager; + private final MinecraftServer minecraftServer; ++ public Runnable playerJoinReady; // Paper + public EntityPlayer player; + private int e; + private long lastKeepAlive = SystemUtils.getMonotonicMillis(); private void setLastPing(long lastPing) { this.lastKeepAlive = lastPing;}; private long getLastPing() { return this.lastKeepAlive;}; // Paper - OBFHELPER +@@ -300,6 +301,15 @@ public class PlayerConnection implements PacketListenerPlayIn { + // CraftBukkit end + + public void tick() { ++ // Paper start - login async ++ Runnable playerJoinReady = this.playerJoinReady; ++ if (playerJoinReady != null) { ++ this.playerJoinReady = null; ++ playerJoinReady.run(); ++ } ++ // Don't tick if not valid (dead), otherwise we load chunks below ++ if (this.player.valid) { ++ // Paper end + this.syncPosition(); + this.player.lastX = this.player.locX(); + this.player.lastY = this.player.locY(); +@@ -341,7 +351,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.r = null; + this.D = false; + this.E = 0; +- } ++ }} // Paper - end if (valid) + + this.minecraftServer.getMethodProfiler().enter("keepAlive"); + // Paper Start - give clients a longer time to respond to pings as per pre 1.12.2 timings +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 7305bba223229d9aa5315a8dff5fdc5af8faba6a..70b3a7c8c4bd817be9c4dfaee71ec22a42620e11 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -41,6 +41,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutEntityStatus; + import net.minecraft.network.protocol.game.PacketPlayOutExperience; + import net.minecraft.network.protocol.game.PacketPlayOutGameStateChange; + import net.minecraft.network.protocol.game.PacketPlayOutHeldItemSlot; ++import net.minecraft.network.protocol.game.PacketPlayOutKickDisconnect; + import net.minecraft.network.protocol.game.PacketPlayOutLogin; + import net.minecraft.network.protocol.game.PacketPlayOutNamedSoundEffect; + import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo; +@@ -60,6 +61,8 @@ import net.minecraft.server.MinecraftServer; + import net.minecraft.server.ScoreboardServer; + import net.minecraft.server.level.DemoPlayerInteractManager; + import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.PlayerChunk; ++import net.minecraft.server.level.PlayerChunkMap; + import net.minecraft.server.level.PlayerInteractManager; + import net.minecraft.server.level.TicketType; + import net.minecraft.server.level.WorldServer; +@@ -128,11 +131,12 @@ public abstract class PlayerList { + private static final SimpleDateFormat g = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z"); + private final MinecraftServer server; + public final List players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety +- private final Map j = Maps.newHashMap(); ++ private final Map j = Maps.newHashMap();Map getUUIDMap() { return j; } // Paper - OBFHELPER + private final GameProfileBanList k; + private final IpBanList l; + private final OpList operators; + private final WhiteList whitelist; ++ private final Map pendingPlayers = Maps.newHashMap(); // Paper + // CraftBukkit start + // private final Map o; + // private final Map p; +@@ -171,6 +175,11 @@ public abstract class PlayerList { + } + + public void a(NetworkManager networkmanager, EntityPlayer entityplayer) { ++ EntityPlayer prev = pendingPlayers.put(entityplayer.getUniqueID(), entityplayer);// Paper ++ if (prev != null) { ++ disconnectPendingPlayer(prev); ++ } ++ entityplayer.networkManager = networkmanager; // Paper + entityplayer.loginTime = System.currentTimeMillis(); // Paper + GameProfile gameprofile = entityplayer.getProfile(); + UserCache usercache = this.server.getUserCache(); +@@ -184,7 +193,7 @@ public abstract class PlayerList { + if (nbttagcompound != null && nbttagcompound.hasKey("bukkit")) { + NBTTagCompound bukkit = nbttagcompound.getCompound("bukkit"); + s = bukkit.hasKeyOfType("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s; +- } ++ }String lastKnownName = s; // Paper + // CraftBukkit end + + if (nbttagcompound != null) { +@@ -259,6 +268,51 @@ public abstract class PlayerList { + entityplayer.getRecipeBook().a(entityplayer); + this.sendScoreboard(worldserver1.getScoreboard(), entityplayer); + this.server.invalidatePingSample(); ++ // Paper start - async load spawn in chunk ++ WorldServer finalWorldserver = worldserver1; ++ int chunkX = loc.getBlockX() >> 4; ++ int chunkZ = loc.getBlockZ() >> 4; ++ final ChunkCoordIntPair pos = new ChunkCoordIntPair(chunkX, chunkZ); ++ PlayerChunkMap playerChunkMap = worldserver1.getChunkProvider().playerChunkMap; ++ playerChunkMap.getChunkDistanceManager().addTicketAtLevel(TicketType.LOGIN, pos, 31, pos.pair()); ++ worldserver1.getChunkProvider().tickDistanceManager(); ++ worldserver1.getChunkProvider().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> { ++ PlayerChunk updatingChunk = playerChunkMap.getUpdatingChunk(pos.pair()); ++ if (updatingChunk != null) { ++ return updatingChunk.getEntityTickingFuture(); ++ } else { ++ return java.util.concurrent.CompletableFuture.completedFuture(chunk); ++ } ++ }).thenAccept(chunk -> { ++ playerconnection.playerJoinReady = () -> { ++ postChunkLoadJoin( ++ entityplayer, finalWorldserver, networkmanager, playerconnection, ++ nbttagcompound, networkmanager.getSocketAddress().toString(), lastKnownName ++ ); ++ }; ++ }); ++ } ++ ++ public EntityPlayer getActivePlayer(UUID uuid) { ++ EntityPlayer player = this.getUUIDMap().get(uuid); ++ return player != null ? player : pendingPlayers.get(uuid); ++ } ++ ++ void disconnectPendingPlayer(EntityPlayer entityplayer) { ++ ChatMessage msg = new ChatMessage("multiplayer.disconnect.duplicate_login", new Object[0]); ++ entityplayer.networkManager.sendPacket(new PacketPlayOutKickDisconnect(msg), (future) -> { ++ entityplayer.networkManager.close(msg); ++ entityplayer.networkManager = null; ++ }); ++ } ++ ++ private void postChunkLoadJoin(EntityPlayer entityplayer, WorldServer worldserver1, NetworkManager networkmanager, PlayerConnection playerconnection, NBTTagCompound nbttagcompound, String s1, String s) { ++ pendingPlayers.remove(entityplayer.getUniqueID(), entityplayer); ++ if (!networkmanager.isConnected()) { ++ return; ++ } ++ entityplayer.didPlayerJoinEvent = true; ++ // Paper end + ChatMessage chatmessage; + + if (entityplayer.getProfile().getName().equalsIgnoreCase(s)) { +@@ -496,6 +550,7 @@ public abstract class PlayerList { + + protected void savePlayerFile(EntityPlayer entityplayer) { + if (!entityplayer.getBukkitEntity().isPersistent()) return; // CraftBukkit ++ if (!entityplayer.didPlayerJoinEvent) return; // Paper - If we never fired PJE, we disconnected during login. Data has not changed, and additionally, our saved vehicle is not loaded! If we save now, we will lose our vehicle (CraftBukkit bug) + this.playerFileData.save(entityplayer); + ServerStatisticManager serverstatisticmanager = (ServerStatisticManager) entityplayer.getStatisticManager(); // CraftBukkit + +@@ -523,7 +578,7 @@ public abstract class PlayerList { + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getName()))); +- cserver.getPluginManager().callEvent(playerQuitEvent); ++ if (entityplayer.didPlayerJoinEvent) cserver.getPluginManager().callEvent(playerQuitEvent); // Paper - if we disconnected before join ever fired, don't fire quit + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + + if (server.isMainThread()) entityplayer.playerTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog) +@@ -576,6 +631,13 @@ public abstract class PlayerList { + // this.p.remove(uuid); + // CraftBukkit end + } ++ // Paper start ++ entityplayer1 = pendingPlayers.get(uuid); ++ if (entityplayer1 == entityplayer) { ++ pendingPlayers.remove(uuid); ++ } ++ entityplayer.networkManager = null; ++ // Paper end + + // CraftBukkit start + // this.sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, new EntityPlayer[]{entityplayer})); +@@ -593,7 +655,7 @@ public abstract class PlayerList { + cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); + // CraftBukkit end + +- return playerQuitEvent.quitMessage(); // Paper - Adventure ++ return entityplayer.didPlayerJoinEvent ? playerQuitEvent.quitMessage() : null; // CraftBukkit // Paper - Adventure // Paper - don't print quit if we never printed join + } + + // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer +@@ -612,6 +674,13 @@ public abstract class PlayerList { + list.add(entityplayer); + } + } ++ // Paper start - check pending players too ++ entityplayer = pendingPlayers.get(uuid); ++ if (entityplayer != null) { ++ this.pendingPlayers.remove(uuid); ++ disconnectPendingPlayer(entityplayer); ++ } ++ // Paper end + + Iterator iterator = list.iterator(); + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7bce3722fb00194f5a913c0b9866b73cfc74611d..8ca7012264528f17ac2e4f15ced96c774fa566d7 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1372,7 +1372,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + this.lastY = d1; + this.lastZ = d4; + this.setPosition(d3, d1, d4); +- world.getChunkAt((int) Math.floor(this.locX()) >> 4, (int) Math.floor(this.locZ()) >> 4); // CraftBukkit ++ if (valid) world.getChunkAt((int) Math.floor(this.locX()) >> 4, (int) Math.floor(this.locZ()) >> 4); // CraftBukkit // Paper + } + + public void d(Vec3D vec3d) { diff --git a/patches/server-unmapped/0001/0455-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch b/patches/server-unmapped/0001/0455-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch new file mode 100644 index 0000000000..49c31930c6 --- /dev/null +++ b/patches/server-unmapped/0001/0455-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch @@ -0,0 +1,27 @@ +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 + +The code following this has better support for null worlds to move +them back to the world spawn. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 8ca7012264528f17ac2e4f15ced96c774fa566d7..675d0cb32efc99f66c4d2b000bf49cf1c065a5e2 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1808,9 +1808,11 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + bworld = server.getWorld(worldName); + } + +- if (bworld == null) { +- bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getWorldServer(World.OVERWORLD).getWorld(); +- } ++ // Paper start - Move player to spawn point if spawn in unloaded world ++// if (bworld == null) { ++// bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getWorldServer(World.OVERWORLD).getWorld(); ++// } ++ // Paper end - Move player to spawn point if spawn in unloaded world + + spawnIn(bworld == null ? null : ((CraftWorld) bworld).getHandle()); + } diff --git a/patches/server-unmapped/0001/0456-Add-PlayerAttackEntityCooldownResetEvent.patch b/patches/server-unmapped/0001/0456-Add-PlayerAttackEntityCooldownResetEvent.patch new file mode 100644 index 0000000000..325aa1c5a1 --- /dev/null +++ b/patches/server-unmapped/0001/0456-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/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index dc9e12c38d1682f6c4558ca07b781de2226c0621..850225509a5398ddcc9335bf88e99bde662bfc91 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -1931,7 +1931,16 @@ public abstract class EntityLiving extends Entity { + + EntityDamageEvent event = CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, hardHat, blocking, armor, resistance, magic, absorption); + if (damagesource.getEntity() instanceof EntityHuman) { +- ((EntityHuman) damagesource.getEntity()).resetAttackCooldown(); // 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 EntityPlayer) { ++ EntityPlayer player = (EntityPlayer) damagesource.getEntity(); ++ if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackCooldown(0F)).callEvent()) { ++ player.resetAttackCooldown(); ++ } ++ } else { ++ ((EntityHuman) damagesource.getEntity()).resetAttackCooldown(); ++ } ++ // Paper end + } + if (event.isCancelled()) { + return false; diff --git a/patches/server-unmapped/0001/0457-Allow-multiple-callbacks-to-schedule-for-Callback-Ex.patch b/patches/server-unmapped/0001/0457-Allow-multiple-callbacks-to-schedule-for-Callback-Ex.patch new file mode 100644 index 0000000000..561eeffb91 --- /dev/null +++ b/patches/server-unmapped/0001/0457-Allow-multiple-callbacks-to-schedule-for-Callback-Ex.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 21 Apr 2020 03:51:53 -0400 +Subject: [PATCH] Allow multiple callbacks to schedule for Callback Executor + +ChunkMapDistance polls multiple entries for pendingChunkUpdates + +Each of these have the potential to move a chunk in and out of +"Loaded" state, which will result in multiple callbacks being +needed within a single tick of ChunkMapDistance + +Use an ArrayDeque to store this Queue + +We make sure to also implement a pattern that is recursion safe too. + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index e5d94dedc88e8bbcf8d9517dfb5c6ec6f62de5aa..042a6beb8cada116d54bed18181de291bf5ed1bb 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -162,24 +162,32 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + public final CallbackExecutor callbackExecutor = new CallbackExecutor(); + public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { + +- private Runnable queued; ++ // Paper start - replace impl with recursive safe multi entry queue ++ // it's possible to schedule multiple tasks currently, so it's vital we change this impl ++ // If we recurse into the executor again, we will append to another queue, ensuring task order consistency ++ private java.util.ArrayDeque queued = new java.util.ArrayDeque<>(); + + @Override + public void execute(Runnable runnable) { +- if (queued != null) { +- throw new IllegalStateException("Already queued"); ++ if (queued == null) { ++ queued = new java.util.ArrayDeque<>(); + } +- queued = runnable; ++ queued.add(runnable); + } + + @Override + public void run() { +- Runnable task = queued; ++ if (queued == null) { ++ return; ++ } ++ java.util.ArrayDeque queue = queued; + queued = null; +- if (task != null) { ++ Runnable task; ++ while ((task = queue.pollFirst()) != null) { + task.run(); + } + } ++ // Paper end + }; + // CraftBukkit end + diff --git a/patches/server-unmapped/0001/0458-Don-t-fire-BlockFade-on-worldgen-threads.patch b/patches/server-unmapped/0001/0458-Don-t-fire-BlockFade-on-worldgen-threads.patch new file mode 100644 index 0000000000..895675ad80 --- /dev/null +++ b/patches/server-unmapped/0001/0458-Don-t-fire-BlockFade-on-worldgen-threads.patch @@ -0,0 +1,28 @@ +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 + +Caused a deadlock + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockFire.java b/src/main/java/net/minecraft/world/level/block/BlockFire.java +index 5ef38414d87fbce453e3ab11579c89a8ff089ae0..e6ea1d29c7f3f4cb6039df0e35c8db94b6f38c3e 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockFire.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockFire.java +@@ -94,6 +94,7 @@ public class BlockFire extends BlockFireAbstract { + @Override + public IBlockData updateState(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { + // CraftBukkit start ++ if (!(generatoraccess instanceof WorldServer)) return this.canPlace(iblockdata, generatoraccess, blockposition) ? (IBlockData) this.a(generatoraccess, blockposition, (Integer) iblockdata.get(BlockFire.AGE)) : Blocks.AIR.getBlockData(); // Paper - don't fire events in world generation + if (!this.canPlace(iblockdata, generatoraccess, blockposition)) { + // Suppress during worldgen + if (!(generatoraccess instanceof World)) { +@@ -109,7 +110,7 @@ public class BlockFire extends BlockFireAbstract { + return blockState.getHandle(); + } + } +- return this.a(generatoraccess, blockposition, (Integer) iblockdata.get(BlockFire.AGE)); ++ return this.a(generatoraccess, blockposition, (Integer) iblockdata.get(BlockFire.AGE)); // Paper - diff on change, see "don't fire events in world generation" + // CraftBukkit end + } + diff --git a/patches/server-unmapped/0001/0459-Add-phantom-creative-and-insomniac-controls.patch b/patches/server-unmapped/0001/0459-Add-phantom-creative-and-insomniac-controls.patch new file mode 100644 index 0000000000..68c8e2168d --- /dev/null +++ b/patches/server-unmapped/0001/0459-Add-phantom-creative-and-insomniac-controls.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 25 Apr 2020 15:13:41 -0500 +Subject: [PATCH] Add phantom creative and insomniac controls + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index cd248eb6be663e8be33f2c3c6b06b77b6d5753a4..46ac6d91422423f1e03b86d3efa3241f2599000d 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -625,4 +625,11 @@ public class PaperWorldConfig { + private void lightQueueSize() { + lightQueueSize = getInt("light-queue-size", lightQueueSize); + } ++ ++ public boolean phantomIgnoreCreative = true; ++ public boolean phantomOnlyAttackInsomniacs = true; ++ private void phantomSettings() { ++ phantomIgnoreCreative = getBoolean("phantoms-do-not-spawn-on-creative-players", phantomIgnoreCreative); ++ phantomOnlyAttackInsomniacs = getBoolean("phantoms-only-attack-insomniacs", phantomOnlyAttackInsomniacs); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/IEntitySelector.java b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +index 1a6f8aec32af85717f5d56e0b00a02cda88ce028..c8d7ea8cfa4945af4a6675172b931a4cc3ca2801 100644 +--- a/src/main/java/net/minecraft/world/entity/IEntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +@@ -3,6 +3,9 @@ package net.minecraft.world.entity; + import com.google.common.base.Predicates; + import java.util.function.Predicate; + import javax.annotation.Nullable; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.stats.StatisticList; ++import net.minecraft.util.MathHelper; + import net.minecraft.world.EnumDifficulty; + import net.minecraft.world.IInventory; + import net.minecraft.world.entity.player.EntityHuman; +@@ -31,6 +34,7 @@ public final class IEntitySelector { + public static final Predicate g = (entity) -> { + return !entity.isSpectator(); + }; ++ public static Predicate isInsomniac = (player) -> MathHelper.clamp(((EntityPlayer) player).getStatisticManager().getStatisticValue(StatisticList.CUSTOM.get(StatisticList.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper + + // Paper start + public static final Predicate affectsSpawning = (entity) -> { +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java b/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java +index 6c498d4345df35a411d155799ac56e47c9c48114..42cf3fa42b73739182d26fbb524ee5b304c799b2 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java +@@ -262,6 +262,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + EntityHuman entityhuman = (EntityHuman) iterator.next(); + + if (EntityPhantom.this.a((EntityLiving) entityhuman, PathfinderTargetCondition.a)) { ++ if (!world.paperConfig.phantomOnlyAttackInsomniacs || IEntitySelector.isInsomniac.test(entityhuman)) // Paper + EntityPhantom.this.setGoalTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit - reason + return true; + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPhantom.java b/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPhantom.java +index 96a5a6569387a25b15a06aaab3bd9d033547e875..e4f5e570636862481aac92ec9b74d6cf5723eb6e 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPhantom.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/MobSpawnerPhantom.java +@@ -53,7 +53,7 @@ public class MobSpawnerPhantom implements MobSpawner { + while (iterator.hasNext()) { + EntityHuman entityhuman = (EntityHuman) iterator.next(); + +- if (!entityhuman.isSpectator()) { ++ if (!entityhuman.isSpectator() && (!worldserver.paperConfig.phantomIgnoreCreative || !entityhuman.isCreative())) { // Paper + BlockPosition blockposition = entityhuman.getChunkCoordinates(); + + if (!worldserver.getDimensionManager().hasSkyLight() || blockposition.getY() >= worldserver.getSeaLevel() && worldserver.e(blockposition)) { diff --git a/patches/server-unmapped/0001/0460-Fix-numerous-item-duplication-issues-and-teleport-is.patch b/patches/server-unmapped/0001/0460-Fix-numerous-item-duplication-issues-and-teleport-is.patch new file mode 100644 index 0000000000..7945a145d7 --- /dev/null +++ b/patches/server-unmapped/0001/0460-Fix-numerous-item-duplication-issues-and-teleport-is.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 25 Apr 2020 06:46:35 -0400 +Subject: [PATCH] Fix numerous item duplication issues 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 675d0cb32efc99f66c4d2b000bf49cf1c065a5e2..cb5c93dca3b947462b89f79c60c7562085684b87 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1969,11 +1969,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } else { + // CraftBukkit start - Capture drops for death event + if (this instanceof EntityLiving && !((EntityLiving) this).forceDrops) { +- ((EntityLiving) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); ++ ((EntityLiving) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // Paper - mirror so we can destroy it later + return null; + } + // CraftBukkit end +- EntityItem entityitem = new EntityItem(this.world, this.locX(), this.locY() + (double) f, this.locZ(), itemstack); ++ EntityItem entityitem = new EntityItem(this.world, this.locX(), this.locY() + (double) f, this.locZ(), itemstack.cloneItemStack()); // Paper - clone so we can destroy original ++ itemstack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + + entityitem.defaultPickupDelay(); + // CraftBukkit start +@@ -2621,6 +2622,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + @Nullable + public Entity teleportTo(WorldServer worldserver, BlockPosition location) { + // CraftBukkit end ++ // Paper start - fix bad state entities causing dupes ++ if (!isAlive() || !valid) { ++ LOGGER.warn("Illegal Entity Teleport " + this + " to " + worldserver + ":" + location, new Throwable()); ++ return null; ++ } ++ // Paper end + if (this.world instanceof WorldServer && !this.dead) { + this.world.getMethodProfiler().enter("changeDimension"); + // CraftBukkit start +@@ -2641,6 +2648,11 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + // CraftBukkit end + + this.world.getMethodProfiler().exitEnter("reloading"); ++ // Paper start - Change lead drop timing to prevent dupe ++ if (this instanceof EntityInsentient) { ++ ((EntityInsentient) this).unleash(true, true); // Paper drop lead ++ } ++ // Paper end + Entity entity = this.getEntityType().a((World) worldserver); + + if (entity != null) { +@@ -2654,10 +2666,6 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + // CraftBukkit start - Forward the CraftEntity to the new entity + this.getBukkitEntity().setHandle(entity); + entity.bukkitEntity = this.getBukkitEntity(); +- +- if (this instanceof EntityInsentient) { +- ((EntityInsentient) this).unleash(true, false); // Unleash to prevent duping of leads. +- } + // CraftBukkit end + } + +@@ -2782,7 +2790,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public boolean canPortal() { +- return true; ++ return isAlive() && valid; // Paper + } + + public float a(Explosion explosion, IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata, Fluid fluid, float f) { +diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +index 69361caebf0d3caa5195b519a16691705ac5e16a..5eb900619951083b9a777b1645cb5495b99968ec 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +@@ -602,7 +602,7 @@ public class EntityArmorStand extends EntityLiving { + for (i = 0; i < this.handItems.size(); ++i) { + itemstack = (ItemStack) this.handItems.get(i); + if (!itemstack.isEmpty()) { +- drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops ++ 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.b); + } + } +@@ -610,7 +610,7 @@ public class EntityArmorStand extends EntityLiving { + for (i = 0; i < this.armorItems.size(); ++i) { + itemstack = (ItemStack) this.armorItems.get(i); + if (!itemstack.isEmpty()) { +- drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops ++ 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.b); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index b5a46134f2aef3e44fca13cd46f1e52d409a58b5..0b6ce7a3c077982a5f341baf3049e6ce66eaa194 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -806,7 +806,8 @@ public class CraftEventFactory { + 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-unmapped/0001/0461-Implement-Brigadier-Mojang-API.patch b/patches/server-unmapped/0001/0461-Implement-Brigadier-Mojang-API.patch new file mode 100644 index 0000000000..09d52a49dc --- /dev/null +++ b/patches/server-unmapped/0001/0461-Implement-Brigadier-Mojang-API.patch @@ -0,0 +1,139 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 19 Apr 2020 18:15:29 -0400 +Subject: [PATCH] Implement 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/src/main/java/net/minecraft/commands/CommandDispatcher.java b/src/main/java/net/minecraft/commands/CommandDispatcher.java +index c97424b401147be53ffa7e2a2a3271d696752efe..07d3dec9f613013aac72f3f5db17089ebe5ee770 100644 +--- a/src/main/java/net/minecraft/commands/CommandDispatcher.java ++++ b/src/main/java/net/minecraft/commands/CommandDispatcher.java +@@ -354,6 +354,7 @@ public class CommandDispatcher { + bukkit.add(node.getName()); + } + // Paper start - Async command map building ++ new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(entityplayer.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper + MinecraftServer.getServer().execute(() -> { + runSync(entityplayer, bukkit, rootcommandnode); + }); +@@ -361,6 +362,7 @@ public class CommandDispatcher { + + private void runSync(EntityPlayer entityplayer, Collection bukkit, RootCommandNode rootcommandnode) { + // Paper end - Async command map building ++ new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(entityplayer.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper + PlayerCommandSendEvent event = new PlayerCommandSendEvent(entityplayer.getBukkitEntity(), new LinkedHashSet<>(bukkit)); + event.getPlayer().getServer().getPluginManager().callEvent(event); + +diff --git a/src/main/java/net/minecraft/commands/CommandListenerWrapper.java b/src/main/java/net/minecraft/commands/CommandListenerWrapper.java +index eb2c9d2248a8647beee9960c5016a83f35aa1247..b5ee789c8dfb7f413ab60902ff3d2ef0cf8273cd 100644 +--- a/src/main/java/net/minecraft/commands/CommandListenerWrapper.java ++++ b/src/main/java/net/minecraft/commands/CommandListenerWrapper.java +@@ -38,7 +38,7 @@ import net.minecraft.world.phys.Vec3D; + + import com.mojang.brigadier.tree.CommandNode; // CraftBukkit + +-public class CommandListenerWrapper implements ICompletionProvider { ++public class CommandListenerWrapper implements ICompletionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource { // Paper + + public static final SimpleCommandExceptionType a = new SimpleCommandExceptionType(new ChatMessage("permissions.requires.player")); + public static final SimpleCommandExceptionType b = new SimpleCommandExceptionType(new ChatMessage("permissions.requires.entity")); +@@ -150,6 +150,25 @@ public class CommandListenerWrapper implements ICompletionProvider { + return this.g; + } + ++ // Paper start ++ @Override ++ public org.bukkit.entity.Entity getBukkitEntity() { ++ return getEntity() != null ? getEntity().getBukkitEntity() : null; ++ } ++ ++ @Override ++ public org.bukkit.World getBukkitWorld() { ++ return getWorld() != null ? getWorld().getWorld() : null; ++ } ++ ++ @Override ++ public org.bukkit.Location getBukkitLocation() { ++ Vec3D pos = getPosition(); ++ org.bukkit.World world = getBukkitWorld(); ++ return world != null && pos != null ? new org.bukkit.Location(world, pos.x, pos.y, pos.z) : null; ++ } ++ // Paper end ++ + @Override + public boolean hasPermission(int i) { + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 443247b03b8352c4dd453270dccdbd7eb5f0944b..b6d326ea30a806240e4a87c277b3cd73a04c805c 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -770,8 +770,12 @@ public class PlayerConnection implements PacketListenerPlayIn { + ParseResults parseresults = this.minecraftServer.getCommandDispatcher().a().parse(stringreader, this.player.getCommandListener()); + + this.minecraftServer.getCommandDispatcher().a().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.networkManager.sendPacket(new PacketPlayOutTabComplete(packetplayintabcomplete.b(), suggestions)); ++ // Paper start ++ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getPlayer(), suggestions, buffer); ++ suggestEvent.setCancelled(suggestions.isEmpty()); ++ if (!suggestEvent.callEvent()) return; ++ this.networkManager.sendPacket(new PacketPlayOutTabComplete(packetplayintabcomplete.b(), (com.mojang.brigadier.suggestion.Suggestions) suggestEvent.getSuggestions())); // CraftBukkit - decompile error // Paper ++ // Paper end + }); + }); + } +@@ -780,7 +784,11 @@ public class PlayerConnection implements PacketListenerPlayIn { + + builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + 1); + completions.forEach(builder::suggest); +- player.playerConnection.sendPacket(new PacketPlayOutTabComplete(packetplayintabcomplete.b(), builder.buildFuture().join())); ++ com.mojang.brigadier.suggestion.Suggestions suggestions = builder.buildFuture().join(); ++ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getPlayer(), suggestions, buffer); ++ suggestEvent.setCancelled(suggestions.isEmpty()); ++ if (!suggestEvent.callEvent()) return; ++ this.networkManager.sendPacket(new PacketPlayOutTabComplete(packetplayintabcomplete.b(), suggestEvent.getSuggestions())); + } + // Paper end - async tab completion + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +index 23d922a8baac01144602fd7813e9e76abc5335a3..8ddd246ad69a2e53749d38c369af701c161de54e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +@@ -17,7 +17,7 @@ import net.minecraft.commands.CommandListenerWrapper; + import org.bukkit.command.Command; + 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; +@@ -28,10 +28,19 @@ 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(); ++ 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(); ++ root.addChild(literal); ++ return literal; ++ // Paper end + } + + @Override diff --git a/patches/server-unmapped/0001/0462-Villager-Restocks-API.patch b/patches/server-unmapped/0001/0462-Villager-Restocks-API.patch new file mode 100644 index 0000000000..07a9844dc0 --- /dev/null +++ b/patches/server-unmapped/0001/0462-Villager-Restocks-API.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: zbk +Date: Sun, 26 Apr 2020 23:49:01 -0400 +Subject: [PATCH] Villager Restocks API + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +index 07f87ee8f5df9d7a40001dd28f50457344308a03..c37dd836c284ed8beac6699ec4f5f91886cf3f63 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +@@ -113,7 +113,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + private long bA; + private int bB; + private long bC; +- private int bD; ++ private int bD; public int getRestocksToday(){ return this.bD; } public void setRestocksToday(int restocksToday){ this.bD = restocksToday; } // Paper OBFHELPER + private long bE; + private boolean bF; + private static final ImmutableList> bG = ImmutableList.of(MemoryModuleType.HOME, MemoryModuleType.JOB_SITE, MemoryModuleType.POTENTIAL_JOB_SITE, MemoryModuleType.MEETING_POINT, MemoryModuleType.MOBS, MemoryModuleType.VISIBLE_MOBS, MemoryModuleType.VISIBLE_VILLAGER_BABIES, MemoryModuleType.NEAREST_PLAYERS, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, MemoryModuleType.WALK_TARGET, new MemoryModuleType[]{MemoryModuleType.LOOK_TARGET, MemoryModuleType.INTERACTION_TARGET, MemoryModuleType.BREED_TARGET, MemoryModuleType.PATH, MemoryModuleType.DOORS_TO_CLOSE, MemoryModuleType.NEAREST_BED, MemoryModuleType.HURT_BY, MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.NEAREST_HOSTILE, MemoryModuleType.SECONDARY_JOB_SITE, MemoryModuleType.HIDING_PLACE, MemoryModuleType.HEARD_BELL_TIME, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.LAST_SLEPT, MemoryModuleType.LAST_WOKEN, MemoryModuleType.LAST_WORKED_AT_POI, MemoryModuleType.GOLEM_DETECTED_RECENTLY}); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +index b020bc9e78a524b4745844ceb99249949c5ab6a4..4b2451179cdda918808ea7001f5033c7e5a8b9ac 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +@@ -84,6 +84,18 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { + getHandle().setExperience(experience); + } + ++ // Paper start ++ @Override ++ public int getRestocksToday() { ++ return getHandle().getRestocksToday(); ++ } ++ ++ @Override ++ public void setRestocksToday(int restocksToday) { ++ getHandle().setRestocksToday(restocksToday); ++ } ++ // Paper end ++ + @Override + public boolean sleep(Location location) { + Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server-unmapped/0001/0463-Validate-PickItem-Packet-and-kick-for-invalid.patch b/patches/server-unmapped/0001/0463-Validate-PickItem-Packet-and-kick-for-invalid.patch new file mode 100644 index 0000000000..37c6dde53b --- /dev/null +++ b/patches/server-unmapped/0001/0463-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/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index b6d326ea30a806240e4a87c277b3cd73a04c805c..6db70005ebc99b19185b8efca550a0783ea05cad 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -883,7 +883,14 @@ public class PlayerConnection implements PacketListenerPlayIn { + @Override + public void a(PacketPlayInPickItem packetplayinpickitem) { + PlayerConnectionUtils.ensureMainThread(packetplayinpickitem, this, this.player.getWorldServer()); +- this.player.inventory.c(packetplayinpickitem.b()); ++ // Paper start - validate pick item position ++ if (!(packetplayinpickitem.b() >= 0 && packetplayinpickitem.b() < this.player.inventory.items.size())) { ++ PlayerConnection.LOGGER.warn("{} tried to set an invalid carried item", this.player.getDisplayName().getString()); ++ this.disconnect("Invalid hotbar selection (Hacking?)"); ++ return; ++ } ++ this.player.inventory.c(packetplayinpickitem.b()); // Paper - Diff above if changed ++ // Paper end + this.player.playerConnection.sendPacket(new PacketPlayOutSetSlot(-2, this.player.inventory.itemInHandIndex, this.player.inventory.getItem(this.player.inventory.itemInHandIndex))); + this.player.playerConnection.sendPacket(new PacketPlayOutSetSlot(-2, packetplayinpickitem.b(), this.player.inventory.getItem(packetplayinpickitem.b()))); + this.player.playerConnection.sendPacket(new PacketPlayOutHeldItemSlot(this.player.inventory.itemInHandIndex)); diff --git a/patches/server-unmapped/0001/0464-Expose-game-version.patch b/patches/server-unmapped/0001/0464-Expose-game-version.patch new file mode 100644 index 0000000000..5d5f8c7da1 --- /dev/null +++ b/patches/server-unmapped/0001/0464-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 55890ff463eb122934e8ba1fc550cf0cccaf8451..96652862531301c08aefa0baa79b1258b5b307ec 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -518,6 +518,13 @@ public final class CraftServer implements Server { + return bukkitVersion; + } + ++ // Paper start - expose game version ++ @Override ++ public String getMinecraftVersion() { ++ return console.getVersion(); ++ } ++ // Paper end ++ + @Override + public List getOnlinePlayers() { + return this.playerView; diff --git a/patches/server-unmapped/0001/0465-Optimize-Voxel-Shape-Merging.patch b/patches/server-unmapped/0001/0465-Optimize-Voxel-Shape-Merging.patch new file mode 100644 index 0000000000..e817b38737 --- /dev/null +++ b/patches/server-unmapped/0001/0465-Optimize-Voxel-Shape-Merging.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 3 May 2020 22:35:09 -0400 +Subject: [PATCH] Optimize Voxel Shape Merging + +This method shows up as super hot in profiler, and also a high "self" time. + +Upon analyzing, it appears most usages of this method fall down to the final +else statement of the nasty ternary. + +Upon even further analyzation, it appears then the majority of those have a +consistent list 1.... One with Infinity head and Tails. + +First optimization is to detect these infinite states and immediately return that +VoxelShapeMergerList so we can avoid testing the rest for most cases. + +Break the method into 2 to help the JVM promote inlining of this fast path. + +Then it was also noticed that VoxelShapeMergerList constructor is also a hotspot +with a high self time... + +Well, knowing that in most cases our list 1 is actualy the same value, it allows +us to know that with an infinite list1, the result on the merger is essentially +list2 as the final values. + +This let us analyze the 2 potential states (Infinite with 2 sources or 4 sources) +and compute a deterministic result for the MergerList values. + +Additionally, this lets us avoid even allocating new objects for this too, further +reducing memory usage. + +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMergerList.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMergerList.java +index c58d380b96e81d65d7c254a9e53017e5157769b0..57a4b4fcb6f89aacadcca49b25153157c8d06cc3 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMergerList.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapeMergerList.java +@@ -6,10 +6,16 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; + + public final class VoxelShapeMergerList implements VoxelShapeMerger { + +- private final DoubleArrayList a; ++ private final DoubleList a; // Paper + private final IntArrayList b; + private final IntArrayList c; + ++ // Paper start ++ private static final IntArrayList INFINITE_B_1 = new IntArrayList(new int[]{1, 1}); ++ private static final IntArrayList INFINITE_B_0 = new IntArrayList(new int[]{0, 0}); ++ private static final IntArrayList INFINITE_C = new IntArrayList(new int[]{0, 1}); ++ // Paper end ++ + protected VoxelShapeMergerList(DoubleList doublelist, DoubleList doublelist1, boolean flag, boolean flag1) { + int i = 0; + int j = 0; +@@ -18,6 +24,22 @@ public final class VoxelShapeMergerList implements VoxelShapeMerger { + int l = doublelist1.size(); + int i1 = k + l; + ++ // Paper start - optimize common path of infinity doublelist ++ int size = doublelist.size(); ++ double tail = doublelist.getDouble(size - 1); ++ double head = doublelist.getDouble(0); ++ if (head == Double.NEGATIVE_INFINITY && tail == Double.POSITIVE_INFINITY && !flag && !flag1 && (size == 2 || size == 4)) { ++ this.a = doublelist1; ++ if (size == 2) { ++ this.b = INFINITE_B_0; ++ } else { ++ this.b = INFINITE_B_1; ++ } ++ this.c = INFINITE_C; ++ return; ++ } ++ // Paper end ++ + this.a = new DoubleArrayList(i1); + this.b = new IntArrayList(i1); + this.c = new IntArrayList(i1); +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +index 24ecac40625629b0bbe460e7fc984b147ede1f1f..2d7405d1fa7c8f378bebe86f5d0de57a129ed92d 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +@@ -329,9 +329,21 @@ public final class VoxelShapes { + } + + @VisibleForTesting +- protected static VoxelShapeMerger a(int i, DoubleList doublelist, DoubleList doublelist1, boolean flag, boolean flag1) { ++ private static VoxelShapeMerger a(int i, DoubleList doublelist, DoubleList doublelist1, boolean flag, boolean flag1) { // Paper - private ++ // Paper start - fast track the most common scenario ++ // doublelist is usually a DoubleArrayList with Infinite head/tails that falls to the final else clause ++ // This is actually the most common path, so jump to it straight away ++ if (doublelist.getDouble(0) == Double.NEGATIVE_INFINITY && doublelist.getDouble(doublelist.size() - 1) == Double.POSITIVE_INFINITY) { ++ return new VoxelShapeMergerList(doublelist, doublelist1, flag, flag1); ++ } ++ // Split out rest to hopefully inline the above ++ return lessCommonMerge(i, doublelist, doublelist1, flag, flag1); ++ } ++ ++ private static VoxelShapeMerger lessCommonMerge(int i, DoubleList doublelist, DoubleList doublelist1, boolean flag, boolean flag1) { + int j = doublelist.size() - 1; + int k = doublelist1.size() - 1; ++ // Paper note - Rewrite below as optimized order if instead of nasty ternary + + if (doublelist instanceof VoxelShapeCubePoint && doublelist1 instanceof VoxelShapeCubePoint) { + long l = a(j, k); +@@ -341,7 +353,22 @@ public final class VoxelShapes { + } + } + +- return (VoxelShapeMerger) (doublelist.getDouble(j) < doublelist1.getDouble(0) - 1.0E-7D ? new VoxelShapeMergerDisjoint(doublelist, doublelist1, false) : (doublelist1.getDouble(k) < doublelist.getDouble(0) - 1.0E-7D ? new VoxelShapeMergerDisjoint(doublelist1, doublelist, true) : (j == k && Objects.equals(doublelist, doublelist1) ? (doublelist instanceof VoxelShapeMergerIdentical ? (VoxelShapeMerger) doublelist : (doublelist1 instanceof VoxelShapeMergerIdentical ? (VoxelShapeMerger) doublelist1 : new VoxelShapeMergerIdentical(doublelist))) : new VoxelShapeMergerList(doublelist, doublelist1, flag, flag1)))); ++ // Identical happens more often than Disjoint ++ if (j == k && Objects.equals(doublelist, doublelist1)) { ++ if (doublelist instanceof VoxelShapeMergerIdentical) { ++ return (VoxelShapeMerger) doublelist; ++ } else if (doublelist1 instanceof VoxelShapeMergerIdentical) { ++ return (VoxelShapeMerger) doublelist1; ++ } ++ return new VoxelShapeMergerIdentical(doublelist); ++ } else if (doublelist.getDouble(j) < doublelist1.getDouble(0) - 1.0E-07) { ++ return new VoxelShapeMergerDisjoint(doublelist, doublelist1, false); ++ } else if (doublelist1.getDouble(k) < doublelist.getDouble(0) - 1.0E-07) { ++ return new VoxelShapeMergerDisjoint(doublelist1, doublelist, true); ++ } else { ++ return new VoxelShapeMergerList(doublelist, doublelist1, flag, flag1); ++ } ++ // Paper end + } + + public interface a { diff --git a/patches/server-unmapped/0001/0466-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch b/patches/server-unmapped/0001/0466-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch new file mode 100644 index 0000000000..d031bd1afa --- /dev/null +++ b/patches/server-unmapped/0001/0466-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 4 May 2020 01:08:56 -0400 +Subject: [PATCH] Set cap on JDK per-thread native byte buffer cache + +See: https://www.evanjones.ca/java-bytebuffer-leak.html + +This is potentially a source of lots of native memory usage. + +We are clearly seeing native usage upwards to 1-4GB which doesn't make sense. + +Region File usage fixed in previous patch should of tecnically only been somewhat +temporary until GC finally gets it some time later, but between all the various +plugins doing IO on various threads, this hidden detail of the JDK could be +keeping long lived large direct buffers in cache. + +Set system properly at server startup if not set already to help protect from this. + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index f9a1f1a83be91e302e9a2d29fefc5b21d9f0e590..154f3a6a3d37b94c40cb29c305e3aa5fb494850c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -22,6 +22,7 @@ public class Main { + + public static void main(String[] args) { + // Todo: Installation script ++ if (System.getProperty("jdk.nio.maxCachedBufferSize") == null) System.setProperty("jdk.nio.maxCachedBufferSize", "262144"); // Paper - cap per-thread NIO cache size + OptionParser parser = new OptionParser() { + { + acceptsAll(asList("?", "help"), "Show the help"); diff --git a/patches/server-unmapped/0001/0467-Implement-Mob-Goal-API.patch b/patches/server-unmapped/0001/0467-Implement-Mob-Goal-API.patch new file mode 100644 index 0000000000..973a6097e3 --- /dev/null +++ b/patches/server-unmapped/0001/0467-Implement-Mob-Goal-API.patch @@ -0,0 +1,1170 @@ +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/pom.xml b/pom.xml +index 325dc0a46666d5e0cd050fd1fb793c08866c2a43..ab57297272c2d6f3d21067081bcaf8775b8fff09 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -149,6 +149,13 @@ + 1.3 + test + ++ ++ ++ io.github.classgraph ++ classgraph ++ 4.8.47 ++ test ++ + + + +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..f62d0ee49ebda2b0c7a136562b24ee038502d048 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +@@ -0,0 +1,530 @@ ++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 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.EntityAgeable; ++import net.minecraft.world.entity.EntityCreature; ++import net.minecraft.world.entity.EntityFlying; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.EntityTameableAnimal; ++import net.minecraft.world.entity.ai.goal.PathfinderGoal; ++import net.minecraft.world.entity.ambient.EntityAmbient; ++import net.minecraft.world.entity.ambient.EntityBat; ++import net.minecraft.world.entity.animal.EntityAnimal; ++import net.minecraft.world.entity.animal.EntityBee; ++import net.minecraft.world.entity.animal.EntityCat; ++import net.minecraft.world.entity.animal.EntityChicken; ++import net.minecraft.world.entity.animal.EntityCod; ++import net.minecraft.world.entity.animal.EntityCow; ++import net.minecraft.world.entity.animal.EntityDolphin; ++import net.minecraft.world.entity.animal.EntityFish; ++import net.minecraft.world.entity.animal.EntityFishSchool; ++import net.minecraft.world.entity.animal.EntityFox; ++import net.minecraft.world.entity.animal.EntityGolem; ++import net.minecraft.world.entity.animal.EntityIronGolem; ++import net.minecraft.world.entity.animal.EntityMushroomCow; ++import net.minecraft.world.entity.animal.EntityOcelot; ++import net.minecraft.world.entity.animal.EntityPanda; ++import net.minecraft.world.entity.animal.EntityParrot; ++import net.minecraft.world.entity.animal.EntityPerchable; ++import net.minecraft.world.entity.animal.EntityPig; ++import net.minecraft.world.entity.animal.EntityPolarBear; ++import net.minecraft.world.entity.animal.EntityPufferFish; ++import net.minecraft.world.entity.animal.EntityRabbit; ++import net.minecraft.world.entity.animal.EntitySalmon; ++import net.minecraft.world.entity.animal.EntitySheep; ++import net.minecraft.world.entity.animal.EntitySnowman; ++import net.minecraft.world.entity.animal.EntitySquid; ++import net.minecraft.world.entity.animal.EntityTropicalFish; ++import net.minecraft.world.entity.animal.EntityTurtle; ++import net.minecraft.world.entity.animal.EntityWaterAnimal; ++import net.minecraft.world.entity.animal.EntityWolf; ++import net.minecraft.world.entity.animal.horse.EntityHorse; ++import net.minecraft.world.entity.animal.horse.EntityHorseAbstract; ++import net.minecraft.world.entity.animal.horse.EntityHorseChestedAbstract; ++import net.minecraft.world.entity.animal.horse.EntityHorseDonkey; ++import net.minecraft.world.entity.animal.horse.EntityHorseMule; ++import net.minecraft.world.entity.animal.horse.EntityHorseSkeleton; ++import net.minecraft.world.entity.animal.horse.EntityHorseZombie; ++import net.minecraft.world.entity.animal.horse.EntityLlama; ++import net.minecraft.world.entity.animal.horse.EntityLlamaTrader; ++import net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon; ++import net.minecraft.world.entity.boss.wither.EntityWither; ++import net.minecraft.world.entity.monster.EntityBlaze; ++import net.minecraft.world.entity.monster.EntityCaveSpider; ++import net.minecraft.world.entity.monster.EntityCreeper; ++import net.minecraft.world.entity.monster.EntityDrowned; ++import net.minecraft.world.entity.monster.EntityEnderman; ++import net.minecraft.world.entity.monster.EntityEndermite; ++import net.minecraft.world.entity.monster.EntityEvoker; ++import net.minecraft.world.entity.monster.EntityGhast; ++import net.minecraft.world.entity.monster.EntityGiantZombie; ++import net.minecraft.world.entity.monster.EntityGuardian; ++import net.minecraft.world.entity.monster.EntityGuardianElder; ++import net.minecraft.world.entity.monster.EntityIllagerAbstract; ++import net.minecraft.world.entity.monster.EntityIllagerIllusioner; ++import net.minecraft.world.entity.monster.EntityIllagerWizard; ++import net.minecraft.world.entity.monster.EntityMagmaCube; ++import net.minecraft.world.entity.monster.EntityMonster; ++import net.minecraft.world.entity.monster.EntityMonsterPatrolling; ++import net.minecraft.world.entity.monster.EntityPhantom; ++import net.minecraft.world.entity.monster.EntityPigZombie; ++import net.minecraft.world.entity.monster.EntityPillager; ++import net.minecraft.world.entity.monster.EntityRavager; ++import net.minecraft.world.entity.monster.EntityShulker; ++import net.minecraft.world.entity.monster.EntitySilverfish; ++import net.minecraft.world.entity.monster.EntitySkeleton; ++import net.minecraft.world.entity.monster.EntitySkeletonAbstract; ++import net.minecraft.world.entity.monster.EntitySkeletonStray; ++import net.minecraft.world.entity.monster.EntitySkeletonWither; ++import net.minecraft.world.entity.monster.EntitySlime; ++import net.minecraft.world.entity.monster.EntitySpider; ++import net.minecraft.world.entity.monster.EntityStrider; ++import net.minecraft.world.entity.monster.EntityVex; ++import net.minecraft.world.entity.monster.EntityVindicator; ++import net.minecraft.world.entity.monster.EntityWitch; ++import net.minecraft.world.entity.monster.EntityZoglin; ++import net.minecraft.world.entity.monster.EntityZombie; ++import net.minecraft.world.entity.monster.EntityZombieHusk; ++import net.minecraft.world.entity.monster.EntityZombieVillager; ++import net.minecraft.world.entity.monster.IRangedEntity; ++import net.minecraft.world.entity.monster.hoglin.EntityHoglin; ++import net.minecraft.world.entity.monster.piglin.EntityPiglin; ++import net.minecraft.world.entity.monster.piglin.EntityPiglinAbstract; ++import net.minecraft.world.entity.monster.piglin.EntityPiglinBrute; ++import net.minecraft.world.entity.npc.EntityVillager; ++import net.minecraft.world.entity.npc.EntityVillagerAbstract; ++import net.minecraft.world.entity.npc.EntityVillagerTrader; ++import net.minecraft.world.entity.raid.EntityRaider; ++import org.bukkit.NamespacedKey; ++import org.bukkit.entity.AbstractHorse; ++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("bee_b", "bee_attack"); ++ deobfuscationMap.put("bee_c", "bee_become_angry"); ++ deobfuscationMap.put("bee_d", "bee_enter_hive"); ++ deobfuscationMap.put("bee_e", "bee_go_to_hive"); ++ deobfuscationMap.put("bee_f", "bee_go_to_known_flower"); ++ deobfuscationMap.put("bee_g", "bee_grow_crop"); ++ deobfuscationMap.put("bee_h", "bee_hurt_by_other"); ++ deobfuscationMap.put("bee_i", "bee_locate_hive"); ++ deobfuscationMap.put("bee_k", "bee_pollinate"); ++ deobfuscationMap.put("bee_l", "bee_wander"); ++ deobfuscationMap.put("cat_a", "cat_avoid_entity"); ++ deobfuscationMap.put("cat_b", "cat_relax_on_owner"); ++ deobfuscationMap.put("dolphin_b", "dolphin_swim_to_treasure"); ++ deobfuscationMap.put("dolphin_c", "dolphin_swim_with_player"); ++ deobfuscationMap.put("dolphin_d", "dolphin_play_with_items"); ++ deobfuscationMap.put("drowned_a", "drowned_attack"); ++ deobfuscationMap.put("drowned_b", "drowned_goto_beach"); ++ deobfuscationMap.put("drowned_c", "drowned_goto_water"); ++ deobfuscationMap.put("drowned_e", "drowned_swim_up"); ++ deobfuscationMap.put("drowned_f", "drowned_trident_attack"); ++ deobfuscationMap.put("enderman_a", "enderman_freeze_when_looked_at"); ++ deobfuscationMap.put("evoker_a", "evoker_attack_spell"); ++ deobfuscationMap.put("evoker_b", "evoker_cast_spell"); ++ deobfuscationMap.put("evoker_c", "evoker_summon_spell"); ++ deobfuscationMap.put("evoker_d", "evoker_wololo_spell"); ++ deobfuscationMap.put("fish_b", "fish_swim"); ++ deobfuscationMap.put("fox_a", "fox_defend_trusted"); ++ deobfuscationMap.put("fox_b", "fox_faceplant"); ++ deobfuscationMap.put("fox_e", "fox_breed"); ++ deobfuscationMap.put("fox_f", "fox_eat_berries"); ++ deobfuscationMap.put("fox_g", "fox_float"); ++ deobfuscationMap.put("fox_h", "fox_follow_parent"); ++ deobfuscationMap.put("fox_j", "fox_look_at_player"); ++ deobfuscationMap.put("fox_l", "fox_melee_attack"); ++ deobfuscationMap.put("fox_n", "fox_panic"); ++ deobfuscationMap.put("fox_o", "fox_pounce"); ++ deobfuscationMap.put("fox_p", "fox_search_for_items"); ++ deobfuscationMap.put("fox_q", "fox_stroll_through_village"); ++ deobfuscationMap.put("fox_r", "fox_perch_and_search"); ++ deobfuscationMap.put("fox_s", "fox_seek_shelter"); ++ deobfuscationMap.put("fox_t", "fox_sleep"); ++ deobfuscationMap.put("fox_u", "fox_stalk_prey"); ++ deobfuscationMap.put("illager_abstract_b", "raider_open_door"); ++ deobfuscationMap.put("illager_illusioner_a", "illusioner_blindness_spell"); ++ deobfuscationMap.put("illager_illusioner_b", "illusioner_mirror_spell"); ++ deobfuscationMap.put("illager_wizard_b", "spellcaster_cast_spell"); ++ deobfuscationMap.put("llama_a", "llama_attack_wolf"); ++ deobfuscationMap.put("llama_c", "llama_hurt_by"); ++ deobfuscationMap.put("llama_trader_a", "llamatrader_defended_wandering_trader"); ++ deobfuscationMap.put("monster_patrolling_a", "long_distance_patrol"); ++ deobfuscationMap.put("ocelot_a", "ocelot_avoid_entity"); ++ deobfuscationMap.put("ocelot_b", "ocelot_tempt"); ++ deobfuscationMap.put("panda_b", "panda_attack"); ++ deobfuscationMap.put("panda_c", "panda_avoid"); ++ deobfuscationMap.put("panda_d", "panda_breed"); ++ deobfuscationMap.put("panda_e", "panda_hurt_by_target"); ++ deobfuscationMap.put("panda_f", "panda_lie_on_back"); ++ deobfuscationMap.put("panda_g", "panda_look_at_player"); ++ deobfuscationMap.put("panda_i", "panda_panic"); ++ deobfuscationMap.put("panda_j", "panda_roll"); ++ deobfuscationMap.put("panda_k", "panda_sit"); ++ deobfuscationMap.put("panda_l", "panda_sneeze"); ++ deobfuscationMap.put("phantom_b", "phantom_attack_player"); ++ deobfuscationMap.put("phantom_c", "phantom_attack_strategy"); ++ deobfuscationMap.put("phantom_e", "phantom_circle_around_anchor"); ++ deobfuscationMap.put("phantom_i", "phantom_sweep_attack"); ++ deobfuscationMap.put("polar_bear_a", "polarbear_attack_players"); ++ deobfuscationMap.put("polar_bear_b", "polarbear_hurt_by"); ++ deobfuscationMap.put("polar_bear_c", "polarbear_melee"); ++ deobfuscationMap.put("polar_bear_d", "polarbear_panic"); ++ deobfuscationMap.put("puffer_fish_a", "pufferfish_puff"); ++ deobfuscationMap.put("raider_a", "raider_hold_ground"); ++ deobfuscationMap.put("raider_b", "raider_obtain_banner"); ++ deobfuscationMap.put("raider_c", "raider_celebration"); ++ deobfuscationMap.put("raider_d", "raider_move_through_village"); ++ deobfuscationMap.put("ravager_a", "ravager_melee_attack"); ++ deobfuscationMap.put("shulker_a", "shulker_attack"); ++ deobfuscationMap.put("shulker_c", "shulker_defense"); ++ deobfuscationMap.put("shulker_d", "shulker_nearest"); ++ deobfuscationMap.put("shulker_e", "shulker_peek"); ++ deobfuscationMap.put("squid_a", "squid_flee"); ++ deobfuscationMap.put("skeleton_abstract_1", "skeleton_melee"); ++ deobfuscationMap.put("strider_a", "strider_go_to_lava"); ++ deobfuscationMap.put("turtle_a", "turtle_breed"); ++ deobfuscationMap.put("turtle_b", "turtle_go_home"); ++ deobfuscationMap.put("turtle_c", "turtle_goto_water"); ++ deobfuscationMap.put("turtle_d", "turtle_lay_egg"); ++ deobfuscationMap.put("turtle_f", "turtle_panic"); ++ deobfuscationMap.put("turtle_h", "turtle_random_stroll"); ++ deobfuscationMap.put("turtle_i", "turtle_tempt"); ++ deobfuscationMap.put("turtle_j", "turtle_travel"); ++ deobfuscationMap.put("vex_a", "vex_charge_attack"); ++ deobfuscationMap.put("vex_b", "vex_copy_target_of_owner"); ++ deobfuscationMap.put("vex_d", "vex_random_move"); ++ deobfuscationMap.put("villager_trader_a", "villagertrader_wander_to_position"); ++ deobfuscationMap.put("vindicator_a", "vindicator_break_door"); ++ deobfuscationMap.put("vindicator_b", "vindicator_johnny_attack"); ++ deobfuscationMap.put("vindicator_c", "vindicator_melee_attack"); ++ deobfuscationMap.put("wither_a", "wither_do_nothing"); ++ deobfuscationMap.put("wolf_a", "wolf_avoid_entity"); ++ deobfuscationMap.put("zombie_a", "zombie_attack_turtle_egg"); ++ ++ ignored.add("selector_1"); ++ ignored.add("selector_2"); ++ ignored.add("wrapped"); ++ ++ bukkitMap.put(EntityInsentient.class, Mob.class); ++ bukkitMap.put(EntityAgeable.class, Ageable.class); ++ bukkitMap.put(EntityAmbient.class, Ambient.class); ++ bukkitMap.put(EntityAnimal.class, Animals.class); ++ bukkitMap.put(EntityBat.class, Bat.class); ++ bukkitMap.put(EntityBee.class, Bee.class); ++ bukkitMap.put(EntityBlaze.class, Blaze.class); ++ bukkitMap.put(EntityCat.class, Cat.class); ++ bukkitMap.put(EntityCaveSpider.class, CaveSpider.class); ++ bukkitMap.put(EntityChicken.class, Chicken.class); ++ bukkitMap.put(EntityCod.class, Cod.class); ++ bukkitMap.put(EntityCow.class, Cow.class); ++ bukkitMap.put(EntityCreature.class, Creature.class); ++ bukkitMap.put(EntityCreeper.class, Creeper.class); ++ bukkitMap.put(EntityDolphin.class, Dolphin.class); ++ bukkitMap.put(EntityDrowned.class, Drowned.class); ++ bukkitMap.put(EntityEnderDragon.class, EnderDragon.class); ++ bukkitMap.put(EntityEnderman.class, Enderman.class); ++ bukkitMap.put(EntityEndermite.class, Endermite.class); ++ bukkitMap.put(EntityEvoker.class, Evoker.class); ++ bukkitMap.put(EntityFish.class, Fish.class); ++ bukkitMap.put(EntityFishSchool.class, Fish.class); // close enough ++ bukkitMap.put(EntityFlying.class, Flying.class); ++ bukkitMap.put(EntityFox.class, Fox.class); ++ bukkitMap.put(EntityGhast.class, Ghast.class); ++ bukkitMap.put(EntityGiantZombie.class, Giant.class); ++ bukkitMap.put(EntityGolem.class, Golem.class); ++ bukkitMap.put(EntityGuardian.class, Guardian.class); ++ bukkitMap.put(EntityGuardianElder.class, ElderGuardian.class); ++ bukkitMap.put(EntityHorse.class, Horse.class); ++ bukkitMap.put(EntityHorseAbstract.class, AbstractHorse.class); ++ bukkitMap.put(EntityHorseChestedAbstract.class, ChestedHorse.class); ++ bukkitMap.put(EntityHorseDonkey.class, Donkey.class); ++ bukkitMap.put(EntityHorseMule.class, Mule.class); ++ bukkitMap.put(EntityHorseSkeleton.class, SkeletonHorse.class); ++ bukkitMap.put(EntityHorseZombie.class, ZombieHorse.class); ++ bukkitMap.put(EntityIllagerAbstract.class, Illager.class); ++ bukkitMap.put(EntityIllagerIllusioner.class, Illusioner.class); ++ bukkitMap.put(EntityIllagerWizard.class, Spellcaster.class); ++ bukkitMap.put(EntityIronGolem.class, IronGolem.class); ++ bukkitMap.put(EntityLlama.class, Llama.class); ++ bukkitMap.put(EntityLlamaTrader.class, TraderLlama.class); ++ bukkitMap.put(EntityMagmaCube.class, MagmaCube.class); ++ bukkitMap.put(EntityMonster.class, Monster.class); ++ bukkitMap.put(EntityMonsterPatrolling.class, Monster.class); // close enough ++ bukkitMap.put(EntityMushroomCow.class, MushroomCow.class); ++ bukkitMap.put(EntityOcelot.class, Ocelot.class); ++ bukkitMap.put(EntityPanda.class, Panda.class); ++ bukkitMap.put(EntityParrot.class, Parrot.class); ++ bukkitMap.put(EntityPerchable.class, Parrot.class); // close enough ++ bukkitMap.put(EntityPhantom.class, Phantom.class); ++ bukkitMap.put(EntityPig.class, Pig.class); ++ bukkitMap.put(EntityPigZombie.class, PigZombie.class); ++ bukkitMap.put(EntityPillager.class, Pillager.class); ++ bukkitMap.put(EntityPolarBear.class, PolarBear.class); ++ bukkitMap.put(EntityPufferFish.class, PufferFish.class); ++ bukkitMap.put(EntityRabbit.class, Rabbit.class); ++ bukkitMap.put(EntityRaider.class, Raider.class); ++ bukkitMap.put(EntityRavager.class, Ravager.class); ++ bukkitMap.put(EntitySalmon.class, Salmon.class); ++ bukkitMap.put(EntitySheep.class, Sheep.class); ++ bukkitMap.put(EntityShulker.class, Shulker.class); ++ bukkitMap.put(EntitySilverfish.class, Silverfish.class); ++ bukkitMap.put(EntitySkeleton.class, Skeleton.class); ++ bukkitMap.put(EntitySkeletonAbstract.class, Skeleton.class); ++ bukkitMap.put(EntitySkeletonStray.class, Stray.class); ++ bukkitMap.put(EntitySkeletonWither.class, WitherSkeleton.class); ++ bukkitMap.put(EntitySlime.class, Slime.class); ++ bukkitMap.put(EntitySnowman.class, Snowman.class); ++ bukkitMap.put(EntitySpider.class, Spider.class); ++ bukkitMap.put(EntitySquid.class, Squid.class); ++ bukkitMap.put(EntityTameableAnimal.class, Tameable.class); ++ bukkitMap.put(EntityTropicalFish.class, TropicalFish.class); ++ bukkitMap.put(EntityTurtle.class, Turtle.class); ++ bukkitMap.put(EntityVex.class, Vex.class); ++ bukkitMap.put(EntityVillager.class, Villager.class); ++ bukkitMap.put(EntityVillagerAbstract.class, AbstractVillager.class); ++ bukkitMap.put(EntityVillagerTrader.class, WanderingTrader.class); ++ bukkitMap.put(EntityVindicator.class, Vindicator.class); ++ bukkitMap.put(EntityWaterAnimal.class, WaterMob.class); ++ bukkitMap.put(EntityWitch.class, Witch.class); ++ bukkitMap.put(EntityWither.class, Wither.class); ++ bukkitMap.put(EntityWolf.class, Wolf.class); ++ bukkitMap.put(EntityZombie.class, Zombie.class); ++ bukkitMap.put(EntityZombieHusk.class, Husk.class); ++ bukkitMap.put(EntityZombieVillager.class, ZombieVillager.class); ++ bukkitMap.put(EntityHoglin.class, Hoglin.class); ++ bukkitMap.put(EntityPiglin.class, Piglin.class); ++ bukkitMap.put(EntityPiglinAbstract.class, PiglinAbstract.class); ++ bukkitMap.put(EntityPiglinBrute.class, PiglinBrute.class); ++ bukkitMap.put(EntityStrider.class, Strider.class); ++ bukkitMap.put(EntityZoglin.class, Zoglin.class); ++ } ++ ++ public static String getUsableName(Class clazz) { ++ String name = 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", ""); ++ 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(OptimizedSmallEnumSet types) { ++ EnumSet goals = EnumSet.noneOf(GoalType.class); ++ for (GoalType type : GoalType.values()) { ++ if (types.hasElement(paperToVanilla(type))) { ++ goals.add(type); ++ } ++ } ++ return goals; ++ } ++ ++ public static GoalType vanillaToPaper(PathfinderGoal.Type 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(PathfinderGoal.Type.class); ++ for (GoalType type : types) { ++ goals.add(paperToVanilla(type)); ++ } ++ return goals; ++ } ++ ++ public static PathfinderGoal.Type paperToVanilla(GoalType type) { ++ switch (type) { ++ case MOVE: ++ return PathfinderGoal.Type.MOVE; ++ case LOOK: ++ return PathfinderGoal.Type.LOOK; ++ case JUMP: ++ return PathfinderGoal.Type.JUMP; ++ case UNKNOWN_BEHAVIOR: ++ return PathfinderGoal.Type.UNKNOWN_BEHAVIOR; ++ case TARGET: ++ return PathfinderGoal.Type.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 (EntityInsentient.class.isAssignableFrom(param)) { ++ //noinspection unchecked ++ return toBukkitClass((Class) param); ++ } else if (IRangedEntity.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..0b963f80857209bc73ece917203447d8839860b6 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java +@@ -0,0 +1,54 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import net.minecraft.world.entity.ai.goal.PathfinderGoal; ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps api in vanilla ++ */ ++public class PaperCustomGoal extends PathfinderGoal { ++ ++ private final Goal handle; ++ ++ public PaperCustomGoal(Goal handle) { ++ this.handle = handle; ++ ++ this.setTypes(MobGoalHelper.paperToVanilla(handle.getTypes())); ++ if (this.getGoalTypes().size() == 0) { ++ this.getGoalTypes().addUnchecked(Type.UNKNOWN_BEHAVIOR); ++ } ++ } ++ ++ @Override ++ public boolean shouldActivate() { ++ return handle.shouldActivate(); ++ } ++ ++ @Override ++ public boolean shouldStayActive() { ++ return handle.shouldStayActive(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void onTaskReset() { ++ 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..bf792c013fe3b9a0b5800a35308b9aaa36e5350d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java +@@ -0,0 +1,222 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import java.util.Collection; ++import java.util.EnumSet; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import net.minecraft.world.entity.ai.goal.PathfinderGoal; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalSelector; ++import net.minecraft.world.entity.ai.goal.PathfinderGoalWrapped; ++import org.bukkit.craftbukkit.entity.CraftMob; ++import org.bukkit.entity.Mob; ++ ++public class PaperMobGoals implements MobGoals { ++ ++ private final Map> instanceCache = new HashMap<>(); ++ ++ @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((PathfinderGoal) goal); ++ } else if (goal instanceof PaperVanillaGoal) { ++ getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal) goal).getHandle()); ++ } else { ++ List toRemove = new LinkedList<>(); ++ for (PathfinderGoalWrapped item : getHandle(craftMob, goal.getTypes()).getTasks()) { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ if (((PaperCustomGoal) item.getGoal()).getHandle() == goal) { ++ toRemove.add(item.getGoal()); ++ } ++ } ++ } ++ ++ for (PathfinderGoal 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 (PathfinderGoalWrapped item : getHandle(craftMob, type).getTasks()) { ++ if (!item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ } ++ 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 (PathfinderGoalWrapped item : getHandle(craftMob, internalType).getTasks()) { ++ if (item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ } ++ } ++ 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).getExecutingGoals() ++ .filter(item -> item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ }); ++ 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).getExecutingGoals() ++ .filter(item -> !item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ }); ++ } ++ return goals; ++ } ++ ++ private PathfinderGoalSelector getHandle(CraftMob mob, EnumSet types) { ++ if (types.contains(GoalType.TARGET)) { ++ return mob.getHandle().targetSelector; ++ } else { ++ return mob.getHandle().goalSelector; ++ } ++ } ++ ++ private PathfinderGoalSelector 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..1ed6e7273bc84a406ca843bc47bb69314bb2dd74 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java +@@ -0,0 +1,62 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import java.util.EnumSet; ++ ++import net.minecraft.world.entity.ai.goal.PathfinderGoal; ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps vanilla in api ++ */ ++public class PaperVanillaGoal implements VanillaGoal { ++ ++ private final PathfinderGoal handle; ++ private final GoalKey key; ++ ++ private final EnumSet types; ++ ++ public PaperVanillaGoal(PathfinderGoal handle) { ++ this.handle = handle; ++ this.key = MobGoalHelper.getKey(handle.getClass()); ++ this.types = MobGoalHelper.vanillaToPaper(handle.getGoalTypes()); ++ } ++ ++ public PathfinderGoal getHandle() { ++ return handle; ++ } ++ ++ @Override ++ public boolean shouldActivate() { ++ return handle.shouldActivate2(); ++ } ++ ++ @Override ++ public boolean shouldStayActive() { ++ return handle.shouldStayActive2(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void stop() { ++ handle.onTaskReset(); ++ } ++ ++ @Override ++ public void tick() { ++ handle.tick(); ++ } ++ ++ @Override ++ public GoalKey getKey() { ++ return key; ++ } ++ ++ @Override ++ public EnumSet getTypes() { ++ return types; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java +index 9df0006c1a283f77c4d01d9fce9062fc1c9bbb1f..b3329c6fcd6758a781a51f5ba8f5052ac1c77b49 100644 +--- a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java ++++ b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java +@@ -64,4 +64,8 @@ public final class OptimizedSmallEnumSet> { + public boolean hasCommonElements(final OptimizedSmallEnumSet other) { + return (other.backingSet & this.backingSet) != 0; + } ++ ++ public boolean hasElement(final E element) { ++ return (this.backingSet & (1L << element.ordinal())) != 0; ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java +index 5c32cbe81c47fcb9ae347faa6fc007c5d28d79bf..59ea1432152051ce8a60c0a526db787593f0e744 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java +@@ -8,11 +8,17 @@ public abstract class PathfinderGoal { + private final EnumSet a = EnumSet.noneOf(PathfinderGoal.Type.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be. + private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Paper - remove streams from pathfindergoalselector + +- public PathfinderGoal() {} ++ // Paper start make sure goaltypes is never empty ++ public PathfinderGoal() { ++ if (this.goalTypes.size() == 0) { ++ this.goalTypes.addUnchecked(Type.UNKNOWN_BEHAVIOR); ++ } ++ } ++ // paper end + +- public abstract boolean a(); ++ public boolean a() { return this.shouldActivate(); } public boolean shouldActivate() { return false;} public boolean shouldActivate2() { return a(); } // Paper - OBFHELPER, for both directions... + +- public boolean b() { ++ public boolean b() { return this.shouldStayActive(); } public boolean shouldStayActive2() { return b(); } public boolean shouldStayActive() { // Paper - OBFHELPER, for both directions... + return this.a(); + } + +@@ -20,19 +26,23 @@ public abstract class PathfinderGoal { + return true; + } + +- public void c() {} ++ public void c() { this.start(); } public void start() {} // Paper - OBFHELPER + + public void d() { + onTaskReset(); // Paper + } + public void onTaskReset() {} // Paper + +- public void e() {} ++ public void e() { this.tick(); } public void tick() {} // Paper OBFHELPER + +- public void a(EnumSet enumset) { ++ public void a(EnumSet enumset) { this.setTypes(enumset); } public void setTypes(EnumSet enumset) { // Paper - OBFHELPER + // Paper start - remove streams from pathfindergoalselector + this.goalTypes.clear(); + this.goalTypes.addAllUnchecked(enumset); ++ // make sure its never empty ++ if (this.goalTypes.size() == 0) { ++ this.goalTypes.addUnchecked(Type.UNKNOWN_BEHAVIOR); ++ } + // Paper end - remove streams from pathfindergoalselector + } + +@@ -48,7 +58,7 @@ public abstract class PathfinderGoal { + + public static enum Type { + +- MOVE, LOOK, JUMP, TARGET; ++ MOVE, LOOK, JUMP, TARGET, UNKNOWN_BEHAVIOR; // Paper - add unknown + + private Type() {} + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java +index 385cd079e264a7e66e91ab3b70b90afb59688dcd..637928664f8c7b1c694a234e507c20724294e450 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalSelector.java +@@ -28,7 +28,7 @@ public class PathfinderGoalSelector { + } + }; + private final Map c = new EnumMap(PathfinderGoal.Type.class); +- private final Set d = Sets.newLinkedHashSet(); private Set getTasks() { return d; }// Paper - OBFHELPER ++ private final Set d = Sets.newLinkedHashSet(); public final Set getTasks() { return d; }// Paper - OBFHELPER // Paper - private -> public + private final Supplier e; + private final EnumSet f = EnumSet.noneOf(PathfinderGoal.Type.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be. + private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Paper - remove streams from pathfindergoalselector +@@ -39,7 +39,7 @@ public class PathfinderGoalSelector { + this.e = supplier; + } + +- public void a(int i, PathfinderGoal pathfindergoal) { ++ public void addGoal(int priority, PathfinderGoal goal) {a(priority, goal);} public void a(int i, PathfinderGoal pathfindergoal) { // Paper - OBFHELPER + this.d.add(new PathfinderGoalWrapped(i, pathfindergoal)); + } + +@@ -58,7 +58,7 @@ public class PathfinderGoalSelector { + } + // Paper end + +- public void a(PathfinderGoal pathfindergoal) { ++ public void removeGoal(PathfinderGoal goal) {a(goal);} public void a(PathfinderGoal pathfindergoal) { // Paper - OBFHELPER + // Paper start - remove streams from pathfindergoalselector + for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { + PathfinderGoalWrapped goalWrapped = iterator.next(); +@@ -154,6 +154,7 @@ public class PathfinderGoalSelector { + gameprofilerfiller.exit(); + } + ++ public final Stream getExecutingGoals() { return d(); } // Paper - OBFHELPER + public Stream d() { + return this.d.stream().filter(PathfinderGoalWrapped::g); + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java +index 8c8e39d35fb56aa6cf7d456adab01dff5d13a60d..bcf6c924894f49f1c602b83b501f904e553235fd 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalWrapped.java +@@ -5,8 +5,8 @@ import javax.annotation.Nullable; + + public class PathfinderGoalWrapped extends PathfinderGoal { + +- private final PathfinderGoal a; +- private final int b; ++ private final PathfinderGoal a; public PathfinderGoal getGoal() {return a;} // Paper - OBFHELPER ++ private final int b; public int getPriority() {return b;} // Paper - OBFHELPER + private boolean c; + + public PathfinderGoalWrapped(int i, PathfinderGoal pathfindergoal) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 96652862531301c08aefa0baa79b1258b5b307ec..3014a5d71de98009bdc121ba690c3653c2eef120 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2385,5 +2385,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/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c0525216a8469613c3e0d4b5774a82f69e70fb16 +--- /dev/null ++++ b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java +@@ -0,0 +1,104 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.ai.goal.PathfinderGoal; ++import org.junit.Assert; ++import org.junit.Test; ++ ++import java.lang.reflect.Field; ++import java.lang.reflect.Modifier; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.stream.Collectors; ++ ++import org.bukkit.entity.Mob; ++ ++import io.github.classgraph.ClassGraph; ++import io.github.classgraph.ScanResult; ++ ++public class VanillaMobGoalTest { ++ ++ @Test ++ public void testKeys() { ++ List> deprecated = new ArrayList<>(); ++ List> keys = new ArrayList<>(); ++ for (Field field : VanillaGoal.class.getFields()) { ++ if (field.getType().equals(GoalKey.class)) { ++ try { ++ GoalKey goalKey = (GoalKey) field.get(null); ++ if (field.getAnnotation(Deprecated.class) != null) { ++ deprecated.add(goalKey); ++ } else { ++ keys.add(goalKey); ++ } ++ } catch (IllegalAccessException e) { ++ System.out.println("Skipping " + field.getName() + ": " + e.getMessage()); ++ } ++ } ++ } ++ ++ List> classes; ++ try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft").scan()) { ++ classes = scanResult.getSubclasses(PathfinderGoal.class.getName()).loadClasses(); ++ } ++ ++ List> vanillaNames = classes.stream() ++ .filter(VanillaMobGoalTest::hasNoEnclosingClass) ++ .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) ++ .map(goalClass -> MobGoalHelper.getKey((Class) goalClass)) ++ .collect(Collectors.toList()); ++ ++ List> missingFromAPI = new ArrayList<>(vanillaNames); ++ missingFromAPI.removeAll(keys); ++ missingFromAPI.removeIf(k -> MobGoalHelper.ignored.contains(k.getNamespacedKey().getKey())); ++ List> missingFromVanilla = new ArrayList<>(keys); ++ missingFromVanilla.removeAll(vanillaNames); ++ ++ boolean shouldFail = false; ++ if (missingFromAPI.size() != 0) { ++ System.out.println("Missing from API: "); ++ for (GoalKey key : missingFromAPI) { ++ System.out.println("GoalKey<" + key.getEntityClass().getSimpleName() + "> " + key.getNamespacedKey().getKey().toUpperCase() + ++ " = GoalKey.of(" + key.getEntityClass().getSimpleName() + ".class, NamespacedKey.minecraft(\"" + key.getNamespacedKey().getKey() + "\"));"); ++ } ++ shouldFail = true; ++ } ++ if (missingFromVanilla.size() != 0) { ++ System.out.println("Missing from vanilla: "); ++ missingFromVanilla.forEach(System.out::println); ++ shouldFail = true; ++ } ++ ++ if (deprecated.size() != 0) { ++ System.out.println("Deprecated (might want to remove them at some point): "); ++ deprecated.forEach(System.out::println); ++ } ++ ++ if (shouldFail) Assert.fail("See above"); ++ } ++ ++ private static boolean hasNoEnclosingClass(Class clazz) { ++ return clazz.getEnclosingClass() == null || hasNoEnclosingClass(clazz.getSuperclass()); ++ } ++ ++ @Test ++ public void testBukkitMap() { ++ List> classes; ++ try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft.world.entity").scan()) { ++ classes = scanResult.getSubclasses("net.minecraft.world.entity.EntityInsentient").loadClasses(); ++ } ++ Assert.assertNotEquals("There are supposed to be more than 0 entity types!", Collections.emptyList(), classes); ++ ++ boolean shouldFail = false; ++ for (Class nmsClass : classes) { ++ Class bukkitClass = MobGoalHelper.toBukkitClass((Class) nmsClass); ++ if (bukkitClass == null) { ++ shouldFail = true; ++ System.out.println("Missing bukkitMap.put(" + nmsClass.getSimpleName() + ".class, " + nmsClass.getSimpleName().replace("Entity", "") + ".class);"); ++ } ++ } ++ ++ if (shouldFail) Assert.fail("See above"); ++ } ++} diff --git a/patches/server-unmapped/0001/0468-Use-distance-map-to-optimise-entity-tracker.patch b/patches/server-unmapped/0001/0468-Use-distance-map-to-optimise-entity-tracker.patch new file mode 100644 index 0000000000..2d9c70c8b6 --- /dev/null +++ b/patches/server-unmapped/0001/0468-Use-distance-map-to-optimise-entity-tracker.patch @@ -0,0 +1,413 @@ +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/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 5c7ded8244187d993d8dda4afff95d9cfc45579b..25cb877dc3879ff5a1bfaf616ba9942f951eba10 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1652,6 +1652,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant list = this.tracker.getPassengers(); + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 042a6beb8cada116d54bed18181de291bf5ed1bb..c30ec3ad68fc10d01d0b3dd1feea32f08c19ab1c 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -61,6 +61,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutMapChunk; + import net.minecraft.network.protocol.game.PacketPlayOutMount; + import net.minecraft.network.protocol.game.PacketPlayOutViewCentre; + import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.progress.WorldLoadListener; + import net.minecraft.util.CSVWriter; + import net.minecraft.util.EntitySlice; +@@ -195,21 +196,55 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + // 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; ++ ++ private int convertSpigotRangeToVanilla(final int vanilla) { ++ return MinecraftServer.getServer().applyTrackingRangeScale(vanilla); ++ } ++ // Paper end - use distance map to optimise tracker + + void addPlayerToDistanceMaps(EntityPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.locX()); + int chunkZ = MCUtil.getChunkCoordinate(player.locZ()); + // Note: players need to be explicitly added to distance maps before they can be updated ++ // 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, this.getEffectiveViewDistance())); ++ } ++ // Paper end - use distance map to optimise entity tracker + } + + void removePlayerFromDistanceMaps(EntityPlayer player) { +- ++ // 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(EntityPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.locX()); + int chunkZ = MCUtil.getChunkCoordinate(player.locZ()); + // Note: players need to be explicitly added to distance maps before they can be updated ++ // 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, this.getEffectiveViewDistance())); ++ } ++ // Paper end - use distance map to optimise entity tracker + } + // Paper end + +@@ -246,6 +281,45 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.m = new VillagePlace(new File(this.w, "poi"), datafixer, flag, this.world); // Paper + this.setViewDistance(i); + this.playerMobDistanceMap = this.world.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper ++ // 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.world.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 = EntityTypes.ENDER_DRAGON.getChunkRange() * 16; ++ 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 + } + + public void updatePlayerMobTypeMap(Entity entity) { +@@ -1484,17 +1558,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + public void movePlayer(EntityPlayer entityplayer) { +- ObjectIterator objectiterator = this.trackedEntities.values().iterator(); +- +- while (objectiterator.hasNext()) { +- PlayerChunkMap.EntityTracker playerchunkmap_entitytracker = (PlayerChunkMap.EntityTracker) objectiterator.next(); +- +- if (playerchunkmap_entitytracker.tracker == entityplayer) { +- playerchunkmap_entitytracker.track(this.world.getPlayers()); +- } else { +- playerchunkmap_entitytracker.updatePlayer(entityplayer); +- } +- } ++ // Paper - delay this logic for the entity tracker tick, no need to duplicate it + + int i = MathHelper.floor(entityplayer.locX()) >> 4; + int j = MathHelper.floor(entityplayer.locZ()) >> 4; +@@ -1610,7 +1674,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker + this.trackedEntities.put(entity.getId(), playerchunkmap_entitytracker); +- playerchunkmap_entitytracker.track(this.world.getPlayers()); ++ playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players + if (entity instanceof EntityPlayer) { + EntityPlayer entityplayer = (EntityPlayer) entity; + +@@ -1653,7 +1717,37 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + entity.tracker = null; // Paper - We're no longer tracked + } + ++ // Paper start - optimised tracker ++ private final void processTrackQueue() { ++ this.world.timings.tracker1.startTiming(); ++ try { ++ for (EntityTracker tracker : this.trackedEntities.values()) { ++ // update tracker entry ++ tracker.updatePlayers(tracker.tracker.getPlayersInTrackRange()); ++ } ++ } finally { ++ this.world.timings.tracker1.stopTiming(); ++ } ++ ++ ++ this.world.timings.tracker2.startTiming(); ++ try { ++ for (EntityTracker tracker : this.trackedEntities.values()) { ++ tracker.trackerEntry.tick(); ++ } ++ } finally { ++ this.world.timings.tracker2.stopTiming(); ++ } ++ } ++ // Paper end - optimised tracker ++ + protected void g() { ++ // Paper start - optimized tracker ++ if (true) { ++ this.processTrackQueue(); ++ return; ++ } ++ // Paper end - optimized tracker + List list = Lists.newArrayList(); + List list1 = this.world.getPlayers(); + +@@ -1722,23 +1816,31 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + PacketDebug.a(this.world, chunk.getPos()); + List list = Lists.newArrayList(); + List list1 = Lists.newArrayList(); +- ObjectIterator objectiterator = this.trackedEntities.values().iterator(); ++ // Paper start - optimise entity tracker ++ // use the chunk entity list, not the whole trackedEntities map... ++ Entity[] entities = chunk.entities.getRawData(); ++ for (int i = 0, size = chunk.entities.size(); i < size; ++i) { ++ Entity entity = entities[i]; ++ if (entity == entityplayer) { ++ continue; ++ } ++ PlayerChunkMap.EntityTracker tracker = this.trackedEntities.get(entity.getId()); ++ if (tracker != null) { // dumb plugins... move on... ++ tracker.updatePlayer(entityplayer); ++ } + +- while (objectiterator.hasNext()) { +- PlayerChunkMap.EntityTracker playerchunkmap_entitytracker = (PlayerChunkMap.EntityTracker) objectiterator.next(); +- Entity entity = playerchunkmap_entitytracker.tracker; ++ // keep the vanilla logic here - this is REQUIRED or else passengers and their vehicles disappear! ++ // (and god knows what the leash thing is) + +- if (entity != entityplayer && entity.chunkX == chunk.getPos().x && entity.chunkZ == chunk.getPos().z) { +- playerchunkmap_entitytracker.updatePlayer(entityplayer); +- if (entity instanceof EntityInsentient && ((EntityInsentient) entity).getLeashHolder() != null) { +- list.add(entity); +- } ++ if (entity instanceof EntityInsentient && ((EntityInsentient)entity).getLeashHolder() != null) { ++ list.add(entity); ++ } + +- if (!entity.getPassengers().isEmpty()) { +- list1.add(entity); +- } ++ if (!entity.getPassengers().isEmpty()) { ++ list1.add(entity); + } + } ++ // Paper end - optimise entity tracker + + Iterator iterator; + Entity entity1; +@@ -1776,7 +1878,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + public class EntityTracker { + +- private final EntityTrackerEntry trackerEntry; ++ final EntityTrackerEntry trackerEntry; // Paper - private -> package private + private final Entity tracker; + private final int trackingDistance; + private SectionPosition e; +@@ -1793,6 +1895,42 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.e = SectionPosition.a(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 EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer)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 (EntityPlayer player : this.trackedPlayers.toArray(new EntityPlayer[0])) { // avoid CME ++ if (newTrackerCandidates == null || !newTrackerCandidates.contains(player)) { ++ this.updatePlayer(player); ++ } ++ } ++ } ++ // Paper end - use distance map to optimise tracker ++ + public boolean equals(Object object) { + return object instanceof PlayerChunkMap.EntityTracker ? ((PlayerChunkMap.EntityTracker) object).tracker.getId() == this.tracker.getId() : false; + } +@@ -1893,7 +2031,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + int j = entity.getEntityType().getChunkRange() * 16; + j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper + +- if (j > i) { ++ if (j < i) { // Paper - we need the lowest range thanks to the fact that our tracker doesn't account for passenger logic + i = j; + } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index cb5c93dca3b947462b89f79c60c7562085684b87..da2b5bfd3966ded2d5dde0d65646583576a088c5 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -46,6 +46,7 @@ import net.minecraft.network.syncher.DataWatcherObject; + import net.minecraft.network.syncher.DataWatcherRegistry; + import net.minecraft.resources.MinecraftKey; + import net.minecraft.resources.ResourceKey; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.PlayerChunkMap; +@@ -295,6 +296,21 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + // CraftBukkit end + ++ // 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() { ++ return ((WorldServer)this.world).getChunkProvider().playerChunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] ++ .getObjectsInRange(MCUtil.getCoordinateKey(this)); ++ } ++ // Paper end - optimise entity tracking ++ + public Entity(EntityTypes entitytypes, World world) { + this.id = Entity.entityCount.incrementAndGet(); + this.passengers = Lists.newArrayList(); +diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java +index 3277a8aaffb6a25624967aa0c62f61309a517739..cd569ad95176fdd0537459b40dfba5c5127a62df 100644 +--- a/src/main/java/org/spigotmc/TrackingRange.java ++++ b/src/main/java/org/spigotmc/TrackingRange.java +@@ -21,6 +21,7 @@ public class TrackingRange + */ + public static int getEntityTrackingRange(Entity entity, int defaultRange) + { ++ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon) return defaultRange; // Paper - enderdragon is exempt + SpigotWorldConfig config = entity.world.spigotConfig; + if ( entity instanceof EntityPlayer ) + { +@@ -44,8 +45,48 @@ public class TrackingRange + return config.miscTrackingRange; + } else + { +- if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon) return ((net.minecraft.server.level.WorldServer)(entity.getWorld())).getChunkProvider().playerChunkMap.getLoadViewDistance(); // Paper - enderdragon is exempt + 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.EntityEnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt ++ if ( entity instanceof EntityPlayer ) ++ { ++ 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 EntityItemFrame || entity instanceof EntityPainting || entity instanceof EntityItem || entity instanceof EntityExperienceOrb ) ++ // Paper end ++ { ++ return TrackingRangeType.MISC; ++ } else ++ { ++ return TrackingRangeType.OTHER; ++ } ++ } ++ ++ public static enum TrackingRangeType { ++ PLAYER, ++ ANIMAL, ++ MONSTER, ++ MISC, ++ OTHER, ++ ENDERDRAGON; ++ } ++ // Paper end - optimise entity tracking + } diff --git a/patches/server-unmapped/0001/0469-Optimize-isOutsideRange-to-use-distance-maps.patch b/patches/server-unmapped/0001/0469-Optimize-isOutsideRange-to-use-distance-maps.patch new file mode 100644 index 0000000000..e4739bbe68 --- /dev/null +++ b/patches/server-unmapped/0001/0469-Optimize-isOutsideRange-to-use-distance-maps.patch @@ -0,0 +1,384 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 5 May 2020 20:40:53 -0700 +Subject: [PATCH] Optimize isOutsideRange to use distance maps + +Use a distance map to find the players in range quickly + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +index 60fcea78bf617559114b1ca1c0bf2d4cd9075a8c..335666db1854e8aa4b2ba71d5bdc2658305cb70a 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +@@ -37,7 +37,7 @@ public abstract class ChunkMapDistance { + private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); + public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); + private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a(); +- private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); ++ public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used + private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); + // Paper start use a queue, but still keep unique requirement + public final java.util.Queue pendingChunkUpdates = new java.util.ArrayDeque() { +@@ -56,6 +56,8 @@ public abstract class ChunkMapDistance { + private final Executor m; + private long currentTick; + ++ PlayerChunkMap chunkMap; // Paper ++ + protected ChunkMapDistance(Executor executor, Executor executor1) { + executor1.getClass(); + Mailbox mailbox = Mailbox.a("player ticket throttler", executor1::execute); +@@ -100,7 +102,7 @@ public abstract class ChunkMapDistance { + protected abstract PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k); + + public boolean a(PlayerChunkMap playerchunkmap) { +- this.f.a(); ++ //this.f.a(); // Paper - no longer used + this.g.a(); + int i = Integer.MAX_VALUE - this.ticketLevelTracker.a(Integer.MAX_VALUE); + boolean flag = i != 0; +@@ -236,7 +238,7 @@ public abstract class ChunkMapDistance { + ((ObjectSet) this.c.computeIfAbsent(i, (j) -> { + return new ObjectOpenHashSet(); + })).add(entityplayer); +- this.f.update(i, 0, true); ++ //this.f.update(i, 0, true); // Paper - no longer used + this.g.update(i, 0, true); + } + +@@ -248,7 +250,7 @@ public abstract class ChunkMapDistance { + if (objectset != null) objectset.remove(entityplayer); // Paper - some state corruption happens here, don't crash, clean up gracefully. + if (objectset == null || objectset.isEmpty()) { // Paper + this.c.remove(i); +- this.f.update(i, Integer.MAX_VALUE, false); ++ //this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used + this.g.update(i, Integer.MAX_VALUE, false); + } + +@@ -272,13 +274,17 @@ public abstract class ChunkMapDistance { + } + + public int b() { +- this.f.a(); +- return this.f.a.size(); ++ // Paper start - use distance map to implement ++ // note: this is the spawn chunk count ++ return this.chunkMap.playerChunkTickRangeMap.size(); ++ // Paper end - use distance map to implement + } + + public boolean d(long i) { +- this.f.a(); +- return this.f.a.containsKey(i); ++ // Paper start - use distance map to implement ++ // note: this is the is spawn chunk method ++ return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(i) != null; ++ // Paper end - use distance map to implement + } + + public String c() { +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index e45457b793ca5e087f976c212020cf6a28183f5e..bb6b447178c410f53b303b8a633297d2f0c09a0d 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -754,6 +754,37 @@ public class ChunkProviderServer extends IChunkProvider { + boolean flag1 = this.world.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && !world.getPlayers().isEmpty(); // CraftBukkit + + if (!flag) { ++ // Paper start - optimize isOutisdeRange ++ PlayerChunkMap playerChunkMap = this.playerChunkMap; ++ for (EntityPlayer player : this.world.players) { ++ if (!player.affectsSpawning || player.isSpectator()) { ++ playerChunkMap.playerMobSpawnMap.remove(player); ++ continue; ++ } ++ ++ int viewDistance = this.playerChunkMap.getEffectiveViewDistance(); ++ ++ // copied and modified from isOutisdeRange ++ int chunkRange = world.spigotConfig.mobSpawnRange; ++ chunkRange = (chunkRange > viewDistance) ? (byte)viewDistance : chunkRange; ++ chunkRange = (chunkRange > ChunkMapDistance.MOB_SPAWN_RANGE) ? ChunkMapDistance.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.playerChunkTickRangeMap.getLastViewDistance(player) == -1) { ++ playerChunkMap.playerMobSpawnMap.remove(player); ++ continue; ++ } ++ ++ int range = Math.min(event.getSpawnRadius(), 32); // limit to max view distance ++ int chunkX = net.minecraft.server.MCUtil.getChunkCoordinate(player.locX()); ++ int chunkZ = net.minecraft.server.MCUtil.getChunkCoordinate(player.locZ()); ++ ++ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range); ++ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in isOutsideRange ++ player.playerNaturallySpawnedEvent = event; ++ } ++ // Paper end - optimize isOutisdeRange + this.world.getMethodProfiler().enter("pollingChunks"); + int k = this.world.getGameRules().getInt(GameRules.RANDOM_TICK_SPEED); + boolean flag2 = world.ticksPerAnimalSpawns != 0L && worlddata.getTime() % world.ticksPerAnimalSpawns == 0L; // CraftBukkit +@@ -783,15 +814,7 @@ public class ChunkProviderServer extends IChunkProvider { + this.world.getMethodProfiler().exit(); + //List list = Lists.newArrayList(this.playerChunkMap.f()); // Paper + //Collections.shuffle(list); // Paper +- //Paper start - call player naturally spawn event +- int chunkRange = world.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > world.spigotConfig.viewDistance) ? (byte) world.spigotConfig.viewDistance : chunkRange; +- chunkRange = Math.min(chunkRange, 8); +- for (EntityPlayer entityPlayer : this.world.getPlayers()) { +- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); +- entityPlayer.playerNaturallySpawnedEvent.callEvent(); +- }; +- // Paper end ++ // Paper - moved up + final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping + Optional optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + +@@ -807,9 +830,9 @@ public class ChunkProviderServer extends IChunkProvider { + Chunk chunk = (Chunk) optional1.get(); + ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); + +- if (!this.playerChunkMap.isOutsideOfRange(chunkcoordintpair)) { ++ if (!this.playerChunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange + chunk.setInhabitedTime(chunk.getInhabitedTime() + j); +- if (flag1 && (this.allowMonsters || this.allowAnimals) && this.world.getWorldBorder().isInBounds(chunk.getPos()) && !this.playerChunkMap.isOutsideOfRange(chunkcoordintpair, true)) { // Spigot ++ if (flag1 && (this.allowMonsters || this.allowAnimals) && this.world.getWorldBorder().isInBounds(chunk.getPos()) && !this.playerChunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, true)) { // Spigot // Paper - optimise isOutsideOfRange + SpawnerCreature.a(this.world, chunk, spawnercreature_d, this.allowAnimals, this.allowMonsters, flag2); + } + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 56a4de74d665b5dba97340bb6d9d35ec112c11f9..ea62c27302d3ce3234ffa421f8c1a585dab0759b 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -249,6 +249,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + ++ double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks ++ + public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) { + super(worldserver, worldserver.getSpawn(), worldserver.v(), gameprofile); + this.spawnDimension = World.OVERWORLD; +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 445dba8ed210407664904b707c36c78a76f25510..25484cac9c62e49de39fbbf506fcb3edc4ba6e65 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -70,6 +70,18 @@ public class PlayerChunk { + long lastAutoSaveTime; // Paper - incremental autosave + long inactiveTimeStart; // Paper - incremental autosave + ++ // Paper start - optimise isOutsideOfRange ++ // cached here to avoid a map lookup ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange; ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInChunkTickRange; ++ ++ void updateRanges() { ++ long key = net.minecraft.server.MCUtil.getCoordinateKey(this.location); ++ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); ++ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); ++ } ++ // Paper end - optimise isOutsideOfRange ++ + public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { + this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); + this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; +@@ -86,6 +98,7 @@ public class PlayerChunk { + this.n = this.oldTicketLevel; + this.a(i); + this.chunkMap = (PlayerChunkMap)playerchunk_d; // Paper ++ this.updateRanges(); // Paper - optimise isOutsideOfRange + } + + // Paper start +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index c30ec3ad68fc10d01d0b3dd1feea32f08c19ab1c..3c49ca30959204840a656c1a44de50a60ea1c7df 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -210,6 +210,17 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return MinecraftServer.getServer().applyTrackingRangeScale(vanilla); + } + // Paper end - use distance map to optimise tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ // A note about the naming used here: ++ // Previously, mojang used a "spawn range" of 8 for controlling both ticking and ++ // mob spawn range. However, spigot makes the spawn range configurable by ++ // checking if the chunk is in the tick range (8) and the spawn range ++ // obviously this means a spawn range > 8 cannot be implemented ++ ++ // these maps are named after spigot's uses ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + + void addPlayerToDistanceMaps(EntityPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.locX()); +@@ -223,6 +234,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); + } + // Paper end - use distance map to optimise entity tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + void removePlayerFromDistanceMaps(EntityPlayer player) { +@@ -231,6 +245,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.playerEntityTrackerTrackMaps[i].remove(player); + } + // Paper end - use distance map to optimise tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerMobSpawnMap.remove(player); ++ this.playerChunkTickRangeMap.remove(player); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + void updateMaps(EntityPlayer player) { +@@ -245,6 +263,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); + } + // Paper end - use distance map to optimise entity tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + // Paper end + +@@ -276,7 +297,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.mailboxWorldGen = this.p.a(threadedmailbox, false); + this.mailboxMain = this.p.a(mailbox, false); + this.lightEngine = new LightEngineThreaded(ilightaccess, this, this.world.getDimensionManager().hasSkyLight(), threadedmailbox1, this.p.a(threadedmailbox1, false)); +- this.chunkDistanceManager = new PlayerChunkMap.a(executor, iasynctaskhandler); ++ this.chunkDistanceManager = new PlayerChunkMap.a(executor, iasynctaskhandler); this.chunkDistanceManager.chunkMap = this; // Paper + this.l = supplier; + this.m = new VillagePlace(new File(this.w, "poi"), datafixer, flag, this.world); // Paper + this.setViewDistance(i); +@@ -320,6 +341,38 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); + } + // Paper end - use distance map to optimise entity tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }); ++ this.playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + public void updatePlayerMobTypeMap(Entity entity) { +@@ -339,6 +392,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return entityPlayer.mobCounts[enumCreatureType.ordinal()]; + } + ++ private static double getDistanceSquaredFromChunk(ChunkCoordIntPair chunkPos, Entity entity) { return a(chunkPos, entity); } // Paper - OBFHELPER + private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) { + double d0 = (double) (chunkcoordintpair.x * 16 + 8); + double d1 = (double) (chunkcoordintpair.z * 16 + 8); +@@ -517,6 +571,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } else { + if (playerchunk != null) { + playerchunk.a(j); ++ playerchunk.updateRanges(); // Paper - optimise isOutsideOfRange + } + + if (playerchunk != null) { +@@ -1487,30 +1542,53 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return isOutsideOfRange(chunkcoordintpair, false); + } + +- boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair, boolean reducedRange) { +- int chunkRange = world.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > world.spigotConfig.viewDistance) ? (byte) world.spigotConfig.viewDistance : chunkRange; +- chunkRange = (chunkRange > 8) ? 8 : chunkRange; ++ // Paper start - optimise isOutsideOfRange ++ final boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair, boolean reducedRange) { ++ return this.isOutsideOfRange(this.getUpdatingChunk(chunkcoordintpair.pair()), chunkcoordintpair, reducedRange); ++ } + +- final int finalChunkRange = chunkRange; // Paper for lambda below +- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event +- // Spigot end +- long i = chunkcoordintpair.pair(); ++ final boolean isOutsideOfRange(PlayerChunk playerchunk, ChunkCoordIntPair chunkcoordintpair, boolean reducedRange) { ++ // this function is so hot that removing the map lookup call can have an order of magnitude impact on its performance ++ // tested and confirmed via System.nanoTime() ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange; + +- return !this.chunkDistanceManager.d(i) ? true : this.playerMap.a(i).noneMatch((entityplayer) -> { +- // Paper start - +- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; +- double blockRange = 16384.0D; +- if (reducedRange) { +- event = entityplayer.playerNaturallySpawnedEvent; +- if (event == null || event.isCancelled()) return false; +- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); +- } ++ if (playersInRange == null) { ++ return true; ++ } + +- return (!entityplayer.isSpectator() && a(chunkcoordintpair, (Entity) entityplayer) < blockRange); // Spigot +- // Paper end +- }); ++ Object[] backingSet = playersInRange.getBackingSet(); ++ ++ if (reducedRange) { ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object raw = backingSet[i]; ++ if (!(raw instanceof EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer) raw; ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (player.lastEntitySpawnRadiusSquared > getDistanceSquaredFromChunk(chunkcoordintpair, player)) { ++ return false; // in range ++ } ++ } ++ } else { ++ final double range = (ChunkMapDistance.MOB_SPAWN_RANGE * 16) * (ChunkMapDistance.MOB_SPAWN_RANGE * 16); ++ // before spigot, mob spawn range was actually mob spawn range + tick range, but it was split ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object raw = backingSet[i]; ++ if (!(raw instanceof EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer) raw; ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (range > getDistanceSquaredFromChunk(chunkcoordintpair, player)) { ++ return false; // in range ++ } ++ } ++ } ++ // no players in range ++ return true; + } ++ // Paper end - optimise isOutsideOfRange + + private boolean b(EntityPlayer entityplayer) { + return entityplayer.isSpectator() && !this.world.getGameRules().getBoolean(GameRules.SPECTATORS_GENERATE_CHUNKS); diff --git a/patches/server-unmapped/0001/0470-Stop-copy-on-write-operations-for-updating-light-dat.patch b/patches/server-unmapped/0001/0470-Stop-copy-on-write-operations-for-updating-light-dat.patch new file mode 100644 index 0000000000..ef0648a1f5 --- /dev/null +++ b/patches/server-unmapped/0001/0470-Stop-copy-on-write-operations-for-updating-light-dat.patch @@ -0,0 +1,319 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 27 Apr 2020 04:05:38 -0700 +Subject: [PATCH] Stop copy-on-write operations for updating light data + +Causes huge memory allocations + gc issues + +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java +index 5f3d2c090d098834e38e447d93f1ea8184c8fb3e..5b1ff4ff87591dd4ff0b79e4ac6ff0494fc3d0f8 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java +@@ -26,8 +26,8 @@ public abstract class LightEngineStorage> e + protected final LongSet b = new LongOpenHashSet(); + protected final LongSet c = new LongOpenHashSet(); + protected final LongSet d = new LongOpenHashSet(); +- protected volatile M e; +- protected final M f; ++ protected volatile M e_visible; protected final Object visibleUpdateLock = new Object(); // Paper - diff on change, should be "visible" - force compile fail on usage change ++ protected final M f; // Paper - diff on change, should be "updating" + protected final LongSet g = new LongOpenHashSet(); + protected final LongSet h = new LongOpenHashSet(); + protected final Long2ObjectMap i = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap()); +@@ -41,8 +41,8 @@ public abstract class LightEngineStorage> e + this.l = enumskyblock; + this.m = ilightaccess; + this.f = m0; +- this.e = m0.b(); +- this.e.d(); ++ this.e_visible = m0.b(); // Paper - avoid copying light data ++ this.e_visible.d(); // Paper - avoid copying light data + } + + protected boolean g(long i) { +@@ -51,7 +51,15 @@ public abstract class LightEngineStorage> e + + @Nullable + protected NibbleArray a(long i, boolean flag) { +- return this.a(flag ? this.f : this.e, i); ++ // Paper start - avoid copying light data ++ if (flag) { ++ return this.a(this.f, i); ++ } else { ++ synchronized (this.visibleUpdateLock) { ++ return this.a(this.e_visible, i); ++ } ++ } ++ // Paper end - avoid copying light data + } + + @Nullable +@@ -364,10 +372,12 @@ public abstract class LightEngineStorage> e + + protected void e() { + if (!this.g.isEmpty()) { ++ synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data + M m0 = this.f.b(); + + m0.d(); +- this.e = m0; ++ this.e_visible = m0; // Paper - avoid copying light data ++ } // Paper - avoid copying light data + this.g.clear(); + } + +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java +index 242a2c5dea1241b515b9eee7c334ab3c31ad9d12..ed7864c552054fc47c6010a094230ce4aebf1c54 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java +@@ -9,10 +9,23 @@ public abstract class LightEngineStorageArray a; +- +- protected LightEngineStorageArray(Long2ObjectOpenHashMap long2objectopenhashmap) { +- this.a = long2objectopenhashmap; ++ protected final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data; // Paper - avoid copying light data ++ protected final boolean isVisible; // Paper - avoid copying light data ++ java.util.function.Function lookup; // Paper - faster branchless lookup ++ ++ // Paper start - avoid copying light data ++ protected LightEngineStorageArray(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data, boolean isVisible) { ++ if (isVisible) { ++ data.performUpdatesLockMap(); ++ } ++ this.data = data; ++ this.isVisible = isVisible; ++ if (isVisible) { ++ lookup = data::getVisibleAsync; ++ } else { ++ lookup = data::getUpdating; ++ } ++ // Paper end - avoid copying light data + this.c(); + this.d = true; + } +@@ -20,16 +33,17 @@ public abstract class LightEngineStorageArray { + + protected LightEngineStorageBlock(ILightAccess ilightaccess) { +- super(EnumSkyBlock.BLOCK, ilightaccess, new LightEngineStorageBlock.a(new Long2ObjectOpenHashMap())); ++ super(EnumSkyBlock.BLOCK, ilightaccess, new LightEngineStorageBlock.a(new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>(), false)); // Paper - avoid copying light data + } + + @Override +@@ -23,13 +23,13 @@ public class LightEngineStorageBlock extends LightEngineStorage { + +- public a(Long2ObjectOpenHashMap long2objectopenhashmap) { +- super(long2objectopenhashmap); ++ public a(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object long2objectopenhashmap, boolean isVisible) { // Paper - avoid copying light data ++ super(long2objectopenhashmap, isVisible); // Paper - avoid copying light data + } + + @Override + public LightEngineStorageBlock.a b() { +- return new LightEngineStorageBlock.a(this.a.clone()); ++ return new a(this.data, true); // Paper - avoid copying light data + } + } + } +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java +index befc8f846c772d58ee687ad427bb71206b4dc43e..64d37f4c6a8167f47c80953a388ea6635490563a 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java +@@ -23,15 +23,16 @@ public class LightEngineStorageSky extends LightEngineStorage(), new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int(), Integer.MAX_VALUE, false)); // Paper - avoid copying light data + } + + @Override + protected int d(long i) { + long j = SectionPosition.e(i); + int k = SectionPosition.c(j); +- LightEngineStorageSky.a lightenginestoragesky_a = (LightEngineStorageSky.a) this.e; +- int l = lightenginestoragesky_a.c.get(SectionPosition.f(j)); ++ synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data ++ LightEngineStorageSky.a lightenginestoragesky_a = (LightEngineStorageSky.a) this.e_visible; // Paper - avoid copying light data - must be after lock acquire ++ int l = lightenginestoragesky_a.otherData.getVisibleAsync(SectionPosition.f(j)); // Paper - avoid copying light data + + if (l != lightenginestoragesky_a.b && k < l) { + NibbleArray nibblearray = this.a(lightenginestoragesky_a, j); // Paper - decompile fix +@@ -52,6 +53,7 @@ public class LightEngineStorageSky extends LightEngineStorage j) { + ((LightEngineStorageSky.a) this.f).b = j; +- ((LightEngineStorageSky.a) this.f).c.defaultReturnValue(((LightEngineStorageSky.a) this.f).b); ++ ((LightEngineStorageSky.a) this.f).otherData.queueDefaultReturnValue(((LightEngineStorageSky.a) this.f).b); // Paper - avoid copying light data + } + + long k = SectionPosition.f(i); +- int l = ((LightEngineStorageSky.a) this.f).c.get(k); ++ int l = ((LightEngineStorageSky.a) this.f).otherData.getUpdating(k); // Paper - avoid copying light data + + if (l < j + 1) { +- ((LightEngineStorageSky.a) this.f).c.put(k, j + 1); ++ ((LightEngineStorageSky.a) this.f).otherData.queueUpdate(k, j + 1); // Paper - avoid copying light data + if (this.o.contains(k)) { + this.q(i); + if (l > ((LightEngineStorageSky.a) this.f).b) { +@@ -107,7 +109,7 @@ public class LightEngineStorageSky extends LightEngineStorage= k; + } +@@ -327,18 +329,21 @@ public class LightEngineStorageSky extends LightEngineStorage { + + private int b; +- private final Long2IntOpenHashMap c; +- +- public a(Long2ObjectOpenHashMap long2objectopenhashmap, Long2IntOpenHashMap long2intopenhashmap, int i) { +- super(long2objectopenhashmap); +- this.c = long2intopenhashmap; +- long2intopenhashmap.defaultReturnValue(i); ++ private final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int otherData; // Paper - avoid copying light data ++ ++ // Paper start - avoid copying light data ++ public a(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data, com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int otherData, int i, boolean isVisible) { ++ super(data, isVisible); ++ this.otherData = otherData; ++ otherData.queueDefaultReturnValue(i); ++ // Paper end - avoid copying light data + this.b = i; + } + + @Override + public LightEngineStorageSky.a b() { +- return new LightEngineStorageSky.a(this.a.clone(), this.c.clone(), this.b); ++ this.otherData.performUpdatesLockMap(); // Paper - avoid copying light data ++ return new LightEngineStorageSky.a(this.data, this.otherData, this.b, true); // Paper - avoid copying light data + } + } + } diff --git a/patches/server-unmapped/0001/0471-No-Tick-view-distance-implementation.patch b/patches/server-unmapped/0001/0471-No-Tick-view-distance-implementation.patch new file mode 100644 index 0000000000..d2a786747d --- /dev/null +++ b/patches/server-unmapped/0001/0471-No-Tick-view-distance-implementation.patch @@ -0,0 +1,774 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 5 May 2020 21:23:34 -0700 +Subject: [PATCH] No-Tick view distance implementation + +Implements world view distance getters/setters + +Per-Player is absent due to difficulty of maintaining +the diff required to make it happen. + +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +index c9164dfdb27ddf3709129c8aec54903a1df121ff..e33e889c291d37a821a4fbd40d9aac7bb079de0d 100644 +--- a/src/main/java/co/aikar/timings/TimingsExport.java ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -153,7 +153,8 @@ public class TimingsExport extends Thread { + pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> { + return pair(rule, world.getWorld().getGameRuleValue(rule)); + })), +- pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance()) ++ pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance()), ++ pair("notick-viewdistance", world.getChunkProvider().playerChunkMap.getEffectiveNoTickViewDistance()) + )); + })); + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 46ac6d91422423f1e03b86d3efa3241f2599000d..6463d3e4837d032a35654a035f42b8a805e0e286 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -632,4 +632,9 @@ public class PaperWorldConfig { + phantomIgnoreCreative = getBoolean("phantoms-do-not-spawn-on-creative-players", phantomIgnoreCreative); + phantomOnlyAttackInsomniacs = getBoolean("phantoms-only-attack-insomniacs", phantomOnlyAttackInsomniacs); + } ++ ++ public int noTickViewDistance; ++ private void viewDistance() { ++ this.noTickViewDistance = this.getInt("viewdistances.no-tick-view-distance", -1); ++ } + } +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 9edbde8299bcd127e1727d34ed441f638e716b2a..17de074111a174f3a39a4477afc3ad62e04a73b5 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -638,7 +638,8 @@ public final class MCUtil { + }); + + worldData.addProperty("name", world.getWorld().getName()); +- worldData.addProperty("view-distance", world.spigotConfig.viewDistance); ++ worldData.addProperty("view-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance()); ++ worldData.addProperty("no-view-distance", world.getChunkProvider().playerChunkMap.getRawNoTickViewDistance()); + worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); + worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange); + worldData.addProperty("visible-chunk-count", visibleChunks.size()); +diff --git a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +index 335666db1854e8aa4b2ba71d5bdc2658305cb70a..2bbdcedf4856080ea9232effdf3bdae9c26c425b 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +@@ -269,7 +269,7 @@ public abstract class ChunkMapDistance { + return s; + } + +- protected void a(int i) { ++ protected void setNoTickViewDistance(int i) { // Paper - force abi breakage on usage change + this.g.a(i); + } + +@@ -388,7 +388,7 @@ public abstract class ChunkMapDistance { + + private void a(long i, int j, boolean flag, boolean flag1) { + if (flag != flag1) { +- Ticket ticket = new Ticket<>(TicketType.PLAYER, ChunkMapDistance.b, new ChunkCoordIntPair(i)); ++ Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkCoordIntPair(i)); // Paper - no-tick view distance + + if (flag1) { + ChunkMapDistance.this.j.a(ChunkTaskQueueSorter.a(() -> { +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index ea62c27302d3ce3234ffa421f8c1a585dab0759b..5b3ccc8e623712cef2afeb16dac001ee4d3423d1 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -251,6 +251,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks + ++ boolean needsChunkCenterUpdate; // Paper - no-tick view distance ++ + public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) { + super(worldserver, worldserver.getSpawn(), worldserver.v(), gameprofile); + this.spawnDimension = World.OVERWORLD; +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 25484cac9c62e49de39fbbf506fcb3edc4ba6e65..1f6333c2c26ad04e23d2881235ed1dcf707be038 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -82,6 +82,18 @@ public class PlayerChunk { + } + // Paper end - optimise isOutsideOfRange + ++ // Paper start - no-tick view distance ++ public final Chunk getSendingChunk() { ++ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used ++ // in Chunk's neighbour callback ++ Chunk ret = this.chunkMap.world.getChunkProvider().getChunkAtIfLoadedImmediately(this.location.x, this.location.z); ++ if (ret != null && ret.areNeighboursLoaded(1)) { ++ return ret; ++ } ++ return null; ++ } ++ // Paper end - no-tick view distance ++ + public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { + this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); + this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; +@@ -241,7 +253,7 @@ public class PlayerChunk { + } + + public void a(BlockPosition blockposition) { +- Chunk chunk = this.getChunk(); ++ Chunk chunk = this.getSendingChunk(); // Paper - no-tick view distance + + if (chunk != null) { + byte b0 = (byte) SectionPosition.a(blockposition.getY()); +@@ -257,7 +269,7 @@ public class PlayerChunk { + } + + public void a(EnumSkyBlock enumskyblock, int i) { +- Chunk chunk = this.getChunk(); ++ Chunk chunk = this.getSendingChunk(); // Paper - no-tick view distance + + if (chunk != null) { + chunk.setNeedsSaving(true); +@@ -339,9 +351,48 @@ public class PlayerChunk { + } + + private void a(Packet packet, boolean flag) { +- this.players.a(this.location, flag).forEach((entityplayer) -> { +- entityplayer.playerConnection.sendPacket(packet); +- }); ++ // Paper start - per player view distance ++ // there can be potential desync with player's last mapped section and the view distance map, so use the ++ // view distance map here. ++ com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerViewDistanceBroadcastMap; ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = viewDistanceMap.getObjectsInRange(this.location); ++ if (players == null) { ++ return; ++ } ++ ++ if (flag) { // flag -> border only ++ Object[] backingSet = players.getBackingSet(); ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object temp = backingSet[i]; ++ if (!(temp instanceof EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer)temp; ++ ++ int viewDistance = viewDistanceMap.getLastViewDistance(player); ++ long lastPosition = viewDistanceMap.getLastCoordinate(player); ++ ++ int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - this.location.x); ++ int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - this.location.z); ++ ++ if (Math.max(distX, distZ) == viewDistance) { ++ player.playerConnection.sendPacket(packet); ++ } ++ } ++ } else { ++ Object[] backingSet = players.getBackingSet(); ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object temp = backingSet[i]; ++ if (!(temp instanceof EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer)temp; ++ player.playerConnection.sendPacket(packet); ++ } ++ } ++ ++ return; ++ // Paper end - per player view distance + } + + public CompletableFuture> a(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) { +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 3c49ca30959204840a656c1a44de50a60ea1c7df..762598b1dc8c6fb4beaad01e5777d0a950845eaf 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -60,9 +60,11 @@ import net.minecraft.network.protocol.game.PacketPlayOutLightUpdate; + import net.minecraft.network.protocol.game.PacketPlayOutMapChunk; + import net.minecraft.network.protocol.game.PacketPlayOutMount; + import net.minecraft.network.protocol.game.PacketPlayOutViewCentre; ++import net.minecraft.network.protocol.game.PacketPlayOutViewDistance; + import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.progress.WorldLoadListener; ++import net.minecraft.server.network.PlayerConnection; + import net.minecraft.util.CSVWriter; + import net.minecraft.util.EntitySlice; + import net.minecraft.util.MathHelper; +@@ -146,7 +148,13 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + private boolean updatingChunksModified; + private final ChunkTaskQueueSorter p; + private final Mailbox> mailboxWorldGen; +- private final Mailbox> mailboxMain; ++ public final Mailbox> mailboxMain; // Paper - private -> public ++ // Paper start ++ final Mailbox> mailboxLight; ++ public void addLightTask(PlayerChunk playerchunk, Runnable run) { ++ this.mailboxLight.a(ChunkTaskQueueSorter.a(playerchunk, run)); ++ } ++ // Paper end + public final WorldLoadListener worldLoadListener; + public final PlayerChunkMap.a chunkDistanceManager; public final ChunkMapDistance getChunkDistanceManager() { return this.chunkDistanceManager; } // Paper - OBFHELPER + private final AtomicInteger u; +@@ -221,6 +229,22 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ // Paper start - no-tick view distance ++ int noTickViewDistance; ++ public final int getRawNoTickViewDistance() { ++ return this.noTickViewDistance; ++ } ++ public final int getEffectiveNoTickViewDistance() { ++ return this.noTickViewDistance == -1 ? this.getEffectiveViewDistance() : this.noTickViewDistance; ++ } ++ public final int getLoadViewDistance() { ++ return Math.max(this.getEffectiveViewDistance(), this.getEffectiveNoTickViewDistance()); ++ } ++ ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceBroadcastMap; ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceTickMap; ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceNoTickMap; ++ // Paper end - no-tick view distance + + void addPlayerToDistanceMaps(EntityPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.locX()); +@@ -237,6 +261,19 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + // Paper start - optimise PlayerChunkMap#isOutsideRange + this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE); + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ // Paper start - no-tick view distance ++ int effectiveTickViewDistance = this.getEffectiveViewDistance(); ++ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance); ++ ++ if (!this.cannotLoadChunks(player)) { ++ this.playerViewDistanceTickMap.add(player, chunkX, chunkZ, effectiveTickViewDistance); ++ this.playerViewDistanceNoTickMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send) ++ } ++ ++ player.needsChunkCenterUpdate = true; ++ this.playerViewDistanceBroadcastMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured ++ player.needsChunkCenterUpdate = false; ++ // Paper end - no-tick view distance + } + + void removePlayerFromDistanceMaps(EntityPlayer player) { +@@ -249,6 +286,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.playerMobSpawnMap.remove(player); + this.playerChunkTickRangeMap.remove(player); + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ // Paper start - no-tick view distance ++ this.playerViewDistanceBroadcastMap.remove(player); ++ this.playerViewDistanceTickMap.remove(player); ++ this.playerViewDistanceNoTickMap.remove(player); ++ // Paper end - no-tick view distance + } + + void updateMaps(EntityPlayer player) { +@@ -266,6 +308,19 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + // Paper start - optimise PlayerChunkMap#isOutsideRange + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE); + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ // Paper start - no-tick view distance ++ int effectiveTickViewDistance = this.getEffectiveViewDistance(); ++ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance); ++ ++ if (!this.cannotLoadChunks(player)) { ++ this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveTickViewDistance); ++ this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send) ++ } ++ ++ player.needsChunkCenterUpdate = true; ++ this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured ++ player.needsChunkCenterUpdate = false; ++ // Paper end - no-tick view distance + } + // Paper end + +@@ -373,6 +428,45 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + }); + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ // Paper start - no-tick view distance ++ this.setNoTickViewDistance(this.world.paperConfig.noTickViewDistance); ++ this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ if (newState.size() != 1) { ++ return; ++ } ++ Chunk chunk = PlayerChunkMap.this.world.getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ); ++ if (chunk == null || !chunk.areNeighboursLoaded(2)) { ++ return; ++ } ++ ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ); ++ PlayerChunkMap.this.world.getChunkProvider().addTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update ++ }, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ if (newState != null) { ++ return; ++ } ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ); ++ PlayerChunkMap.this.world.getChunkProvider().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update ++ }); ++ this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); ++ this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ if (player.needsChunkCenterUpdate) { ++ player.needsChunkCenterUpdate = false; ++ player.playerConnection.sendPacket(new PacketPlayOutViewCentre(currPosX, currPosZ)); ++ } ++ PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), new Packet[2], false, true); // unloaded, loaded ++ }, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), null, true, false); // unloaded, loaded ++ }); ++ // Paper end - no-tick view distance + } + + public void updatePlayerMobTypeMap(Entity entity) { +@@ -1193,15 +1287,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + completablefuture1.thenAcceptAsync((either) -> { + either.mapLeft((chunk) -> { + this.u.getAndIncrement(); +- Packet[] apacket = new Packet[2]; +- +- this.a(chunkcoordintpair, false).forEach((entityplayer) -> { +- this.a(entityplayer, apacket, chunk); +- }); ++ // Paper - no-tick view distance - moved to Chunk neighbour update + return Either.left(chunk); + }); + }, (runnable) -> { +- this.mailboxMain.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); ++ this.mailboxMain.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // Paper - diff on change, this is the scheduling method copied in Chunk used to schedule chunk broadcasts (on change it needs to be copied again) + }); + return completablefuture1; + } +@@ -1296,32 +1386,38 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + } + +- protected void setViewDistance(int i) { +- int j = MathHelper.clamp(i + 1, 3, 33); ++ public void setViewDistance(int i) { // Paper - public ++ int j = MathHelper.clamp(i + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32 + + if (j != this.viewDistance) { + int k = this.viewDistance; + + this.viewDistance = j; +- this.chunkDistanceManager.a(this.viewDistance); +- ObjectIterator objectiterator = this.updatingChunks.values().iterator(); ++ this.setNoTickViewDistance(this.getRawNoTickViewDistance()); //Paper - no-tick view distance - propagate changes to no-tick, which does the actual chunk loading/sending ++ } + +- while (objectiterator.hasNext()) { +- PlayerChunk playerchunk = (PlayerChunk) objectiterator.next(); +- ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); +- Packet[] apacket = new Packet[2]; ++ } + +- this.a(chunkcoordintpair, false).forEach((entityplayer) -> { +- int l = b(chunkcoordintpair, entityplayer, true); +- boolean flag = l <= k; +- boolean flag1 = l <= this.viewDistance; ++ // Paper start - no-tick view distance ++ public final void setNoTickViewDistance(int viewDistance) { ++ viewDistance = viewDistance == -1 ? -1 : MathHelper.clamp(viewDistance, 2, 32); + +- this.sendChunk(entityplayer, chunkcoordintpair, apacket, flag, flag1); +- }); ++ this.noTickViewDistance = viewDistance; ++ int loadViewDistance = this.getLoadViewDistance(); ++ this.chunkDistanceManager.setNoTickViewDistance(loadViewDistance + 2 + 2); // add 2 to account for the change to 31 -> 33 tickets // see notes in the distance map updating for the other + 2 ++ ++ if (this.world != null && this.world.players != null) { // this can be called from constructor, where these aren't set ++ for (EntityPlayer player : this.world.players) { ++ PlayerConnection connection = player.playerConnection; ++ if (connection != null) { ++ // moved in from PlayerList ++ connection.sendPacket(new PacketPlayOutViewDistance(loadViewDistance)); ++ } ++ this.updateMaps(player); + } + } +- + } ++ // Paper end - no-tick view distance + + protected void sendChunk(EntityPlayer entityplayer, ChunkCoordIntPair chunkcoordintpair, Packet[] apacket, boolean flag, boolean flag1) { + if (entityplayer.world == this.world) { +@@ -1329,7 +1425,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + PlayerChunk playerchunk = this.getVisibleChunk(chunkcoordintpair.pair()); + + if (playerchunk != null) { +- Chunk chunk = playerchunk.getChunk(); ++ Chunk chunk = playerchunk.getSendingChunk(); // Paper - no-tick view distance + + if (chunk != null) { + this.a(entityplayer, apacket, chunk); +@@ -1590,6 +1686,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + // Paper end - optimise isOutsideOfRange + ++ private boolean cannotLoadChunks(EntityPlayer entityplayer) { return this.b(entityplayer); } // Paper - OBFHELPER + private boolean b(EntityPlayer entityplayer) { + return entityplayer.isSpectator() && !this.world.getGameRules().getBoolean(GameRules.SPECTATORS_GENERATE_CHUNKS); + } +@@ -1617,13 +1714,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.removePlayerFromDistanceMaps(entityplayer); // Paper - distance maps + } + +- for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) { +- for (int l = j - this.viewDistance; l <= j + this.viewDistance; ++l) { +- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(k, l); +- +- this.sendChunk(entityplayer, chunkcoordintpair, new Packet[2], !flag, flag); +- } +- } ++ // Paper - broadcast view distance map handles this (see remove/add calls above) + + } + +@@ -1631,7 +1722,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + SectionPosition sectionposition = SectionPosition.a((Entity) entityplayer); + + entityplayer.a(sectionposition); +- entityplayer.playerConnection.sendPacket(new PacketPlayOutViewCentre(sectionposition.a(), sectionposition.c())); ++ // Paper - distance map handles this now + return sectionposition; + } + +@@ -1676,6 +1767,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + int k1; + int l1; + ++ /* // Paper start - replaced by distance map + if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) { + k1 = Math.min(i, i1) - this.viewDistance; + l1 = Math.min(j, j1) - this.viewDistance; +@@ -1713,7 +1805,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.sendChunk(entityplayer, chunkcoordintpair1, new Packet[2], false, true); + } + } +- } ++ }*/ // Paper end - replaced by distance map + + this.updateMaps(entityplayer); // Paper - distance maps + +@@ -1721,11 +1813,46 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + @Override + public Stream a(ChunkCoordIntPair chunkcoordintpair, boolean flag) { +- return this.playerMap.a(chunkcoordintpair.pair()).filter((entityplayer) -> { +- int i = b(chunkcoordintpair, entityplayer, true); ++ // Paper start - per player view distance ++ // there can be potential desync with player's last mapped section and the view distance map, so use the ++ // view distance map here. ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = this.playerViewDistanceBroadcastMap.getObjectsInRange(chunkcoordintpair); + +- return i > this.viewDistance ? false : !flag || i == this.viewDistance; +- }); ++ if (inRange == null) { ++ return Stream.empty(); ++ } ++ // all current cases are inlined so we wont hit this code, it's just in case plugins or future updates use it ++ List players = new java.util.ArrayList<>(); ++ Object[] backingSet = inRange.getBackingSet(); ++ ++ if (flag) { // flag -> border only ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object temp = backingSet[i]; ++ if (!(temp instanceof EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer)temp; ++ int viewDistance = this.playerViewDistanceBroadcastMap.getLastViewDistance(player); ++ long lastPosition = this.playerViewDistanceBroadcastMap.getLastCoordinate(player); ++ ++ int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - chunkcoordintpair.x); ++ int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - chunkcoordintpair.z); ++ if (Math.max(distX, distZ) == viewDistance) { ++ players.add(player); ++ } ++ } ++ } else { ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object temp = backingSet[i]; ++ if (!(temp instanceof EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer)temp; ++ players.add(player); ++ } ++ } ++ return players.stream(); ++ // Paper end - per player view distance + } + + public void addEntity(Entity entity) { // Paper - protected -> public +@@ -1883,7 +2010,48 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + } + +- private final void sendChunk(EntityPlayer entityplayer, Packet[] apacket, Chunk chunk) { this.a(entityplayer, apacket, chunk); } // Paper - OBFHELPER ++ // Paper start ++ private static int getLightMask(final Chunk chunk) { ++ final ChunkSection[] chunkSections = chunk.getSections(); ++ int mask = 0; ++ ++ for (int i = 0; i < chunkSections.length; ++i) { ++ /* ++ ++ ++Lightmasks have 18 bits, from the -1 (void) section until the 17th (air) section. ++Sections go from 0..16. Now whenever a section is not empty, it can potentially change lighting for the section itself, the section below and the section above, hence the bitmask 111b, which is 7d. ++ ++ */ ++ mask |= (ChunkSection.isEmpty(chunkSections[i]) ? 0 : 7) << i; ++ } ++ ++ return mask; ++ } ++ ++ private static int getCeilingLightMask(final Chunk chunk) { ++ int mask = getLightMask(chunk); ++ ++ /* ++ It is similar to get highest bit, it would turn an 001010 into an 001111 so basically the highest bit and all below. ++ We then invert this, so we'd have 110000 and compare that to the "main" chunk. ++ This is because the bug only appears when the current chunks lightmaps are higher than those of the neighbors, thus we can omit sending neighbors which are lower than the current chunks lights. ++ ++ so TLDR is that getCeilingLightMask returns a light mask with all bits set below the highest affected section. We could also count the number of leading zeros and invert them, somehow. ++ @TODO: Implement Leafs suggestion ++ either use Integer#numberOfLeadingZeros or document what this bithack is supposed to be doing then ++ */ ++ mask |= mask >> 1; ++ mask |= mask >> 2; ++ mask |= mask >> 4; ++ mask |= mask >> 8; ++ mask |= mask >> 16; ++ ++ return mask; ++ } ++ // Paper end ++ ++ public final void sendChunk(EntityPlayer entityplayer, Packet[] apacket, Chunk chunk) { this.a(entityplayer, apacket, chunk); } // Paper - OBFHELPER + private void a(EntityPlayer entityplayer, Packet[] apacket, Chunk chunk) { + if (apacket[0] == null) { + apacket[0] = new PacketPlayOutMapChunk(chunk, 65535, chunk.world.chunkPacketBlockController.shouldModify(entityplayer, chunk, 65535)); // Paper - Anti-Xray - Bypass +@@ -2069,7 +2237,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(this.tracker.chunkX, this.tracker.chunkZ); + PlayerChunk playerchunk = PlayerChunkMap.this.getVisibleChunk(chunkcoordintpair.pair()); + +- if (playerchunk != null && playerchunk.getChunk() != null) { ++ if (playerchunk != null && playerchunk.getSendingChunk() != null) { // Paper - no-tick view distance + flag1 = PlayerChunkMap.b(chunkcoordintpair, entityplayer, false) <= PlayerChunkMap.this.viewDistance; + } + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 70b3a7c8c4bd817be9c4dfaee71ec22a42620e11..ee6de0186f6a112d02e1dd4cc73fdaa92ab4d0d9 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -254,7 +254,7 @@ public abstract class PlayerList { + boolean flag1 = gamerules.getBoolean(GameRules.REDUCED_DEBUG_INFO); + + // Spigot - view distance +- playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), entityplayer.playerInteractManager.c(), BiomeManager.a(worldserver1.getSeed()), worlddata.isHardcore(), this.server.F(), this.s, worldserver1.getDimensionManager(), worldserver1.getDimensionKey(), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, flag1, !flag, worldserver1.isDebugWorld(), worldserver1.isFlatWorld())); ++ playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), entityplayer.playerInteractManager.c(), BiomeManager.a(worldserver1.getSeed()), worlddata.isHardcore(), this.server.F(), this.s, worldserver1.getDimensionManager(), worldserver1.getDimensionKey(), this.getMaxPlayers(), worldserver1.getChunkProvider().playerChunkMap.getLoadViewDistance(), flag1, !flag, worldserver1.isDebugWorld(), worldserver1.isFlatWorld())); // Paper - no-tick view distance + entityplayer.getBukkitEntity().sendSupportedChannels(); // CraftBukkit + playerconnection.sendPacket(new PacketPlayOutCustomPayload(PacketPlayOutCustomPayload.a, (new PacketDataSerializer(Unpooled.buffer())).a(this.getServer().getServerModName()))); + playerconnection.sendPacket(new PacketPlayOutServerDifficulty(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); +@@ -908,7 +908,7 @@ public abstract class PlayerList { + // CraftBukkit start + WorldData worlddata = worldserver1.getWorldData(); + entityplayer1.playerConnection.sendPacket(new PacketPlayOutRespawn(worldserver1.getDimensionManager(), worldserver1.getDimensionKey(), BiomeManager.a(worldserver1.getSeed()), entityplayer1.playerInteractManager.getGameMode(), entityplayer1.playerInteractManager.c(), worldserver1.isDebugWorld(), worldserver1.isFlatWorld(), flag)); +- entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(worldserver1.spigotConfig.viewDistance)); // Spigot ++ entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(worldserver1.getChunkProvider().playerChunkMap.getLoadViewDistance())); // Spigot // Paper - no-tick view distance + entityplayer1.spawnIn(worldserver1); + entityplayer1.dead = false; + entityplayer1.playerConnection.teleport(new Location(worldserver1.getWorld(), entityplayer1.locX(), entityplayer1.locY(), entityplayer1.locZ(), entityplayer1.yaw, entityplayer1.pitch)); +@@ -1376,7 +1376,7 @@ public abstract class PlayerList { + + public void a(int i) { + this.viewDistance = i; +- this.sendAll(new PacketPlayOutViewDistance(i)); ++ //this.sendAll(new PacketPlayOutViewDistance(i)); // Paper - move into setViewDistance + Iterator iterator = this.server.getWorlds().iterator(); + + while (iterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index efcfc8f0f45901d14ac8fdf8ed7b0bd67f8f94da..7ead848342bfbb5b20e95d716805f4b4fd36eb63 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -525,8 +525,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + this.b(blockposition, iblockdata1, iblockdata2); + } + +- if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getState() != null && chunk.getState().isAtLeast(PlayerChunk.State.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement ++ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getState() != null && chunk.getState().isAtLeast(PlayerChunk.State.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement // Paper - diff on change, see below + this.notify(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) && (this.isClientSide || chunk == null || ((WorldServer)this).getChunkProvider().playerChunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) { ++ ((WorldServer)this).getChunkProvider().flagDirty(blockposition); ++ // Paper end - per player view distance + } + + if ((i & 1) != 0) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index bb2ff043f0d159fa18769c31b08683ee12037c58..90e895e9eac6158a28de4a30589bf7538e5ec9cc 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -28,7 +28,12 @@ import net.minecraft.core.IRegistry; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkProviderServer; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.server.level.ChunkTaskQueueSorter; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.PlayerChunk; ++import net.minecraft.server.level.PlayerChunkMap; ++import net.minecraft.server.level.TicketType; + import net.minecraft.server.level.WorldServer; + import net.minecraft.util.EntitySlice; + import net.minecraft.util.MathHelper; +@@ -243,7 +248,51 @@ public class Chunk implements IChunkAccess { + } + + protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { ++ // Paper start - no-tick view distance ++ ChunkProviderServer chunkProviderServer = ((WorldServer)this.world).getChunkProvider(); ++ PlayerChunkMap chunkMap = chunkProviderServer.playerChunkMap; ++ // this code handles the addition of ticking tickets - the distance map handles the removal ++ if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) { ++ if (chunkMap.playerViewDistanceTickMap.getObjectsInRange(this.coordinateKey) != null) { ++ // now we're ready for entity ticking ++ chunkProviderServer.serverThreadQueue.execute(() -> { ++ // double check that this condition still holds. ++ if (Chunk.this.areNeighboursLoaded(2) && chunkMap.playerViewDistanceTickMap.getObjectsInRange(Chunk.this.coordinateKey) != null) { ++ chunkProviderServer.addTicketAtLevel(TicketType.PLAYER, Chunk.this.loc, 31, Chunk.this.loc); // 31 -> entity ticking, TODO check on update ++ } ++ }); ++ } ++ } + ++ // this code handles the chunk sending ++ if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) { ++ if (chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(this.coordinateKey) != null) { ++ // now we're ready to send ++ chunkMap.mailboxMain.a(ChunkTaskQueueSorter.a(chunkMap.getUpdatingChunk(this.coordinateKey), (() -> { // Copied frm PlayerChunkMap ++ // double check that this condition still holds. ++ if (!Chunk.this.areNeighboursLoaded(1)) { ++ return; ++ } ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(Chunk.this.coordinateKey); ++ if (inRange == null) { ++ return; ++ } ++ ++ // broadcast ++ Object[] backingSet = inRange.getBackingSet(); ++ Packet[] chunkPackets = new Packet[2]; ++ for (int index = 0, len = backingSet.length; index < len; ++index) { ++ Object temp = backingSet[index]; ++ if (!(temp instanceof EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer)temp; ++ chunkMap.sendChunk(player, chunkPackets, Chunk.this); ++ } ++ }))); ++ } ++ } ++ // Paper end - no-tick view distance + } + + public final boolean isAnyNeighborsLoaded() { +@@ -1132,7 +1181,7 @@ public class Chunk implements IChunkAccess { + IBlockData iblockdata = this.getType(blockposition); + IBlockData iblockdata1 = Block.b(iblockdata, (GeneratorAccess) this.world, blockposition); + +- this.world.setTypeAndData(blockposition, iblockdata1, 20); ++ this.world.setTypeAndData(blockposition, iblockdata1, 20 | 2); // Paper - We send chunks before they're ticking ready, so we need to notify here + } + + this.n[i].clear(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index a6c1ef15784f7ae7bc703e5bc24cd2c97ad5b1a8..400499f32c59dcb3e850f5b52d84d4564c42c033 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -32,6 +32,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutWorldEvent; + import net.minecraft.resources.MinecraftKey; + import net.minecraft.server.level.ChunkMapDistance; + import net.minecraft.server.level.PlayerChunk; ++import net.minecraft.server.level.PlayerChunkMap; + import net.minecraft.server.level.Ticket; + import net.minecraft.server.level.TicketType; + import net.minecraft.server.level.WorldServer; +@@ -2550,10 +2551,39 @@ public class CraftWorld implements World { + // Spigot start + @Override + public int getViewDistance() { +- return world.spigotConfig.viewDistance; ++ return getHandle().getChunkProvider().playerChunkMap.getEffectiveViewDistance(); // Paper - no-tick view distance + } + // Spigot end + ++ // Paper start - per player view distance ++ @Override ++ public void setViewDistance(int viewDistance) { ++ if (viewDistance < 2 || viewDistance > 32) { ++ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); ++ } ++ PlayerChunkMap chunkMap = getHandle().getChunkProvider().playerChunkMap; ++ if (viewDistance != chunkMap.getEffectiveViewDistance()) { ++ chunkMap.setViewDistance(viewDistance); ++ } ++ } ++ ++ @Override ++ public int getNoTickViewDistance() { ++ return getHandle().getChunkProvider().playerChunkMap.getEffectiveNoTickViewDistance(); ++ } ++ ++ @Override ++ public void setNoTickViewDistance(int viewDistance) { ++ if ((viewDistance < 2 || viewDistance > 32) && viewDistance != -1) { ++ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); ++ } ++ PlayerChunkMap chunkMap = getHandle().getChunkProvider().playerChunkMap; ++ if (viewDistance != chunkMap.getRawNoTickViewDistance()) { ++ chunkMap.setNoTickViewDistance(viewDistance); ++ } ++ } ++ // Paper end - per player view distance ++ + // Spigot start + private final org.bukkit.World.Spigot spigot = new org.bukkit.World.Spigot() + { +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 663127e6e6ec507959142b18a11a5a4790d4b98b..5c2eaca0bc63c7880ee928aba6a24761737aa649 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -2,6 +2,7 @@ package org.spigotmc; + + import java.util.Collection; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.WorldServer; + import net.minecraft.util.MathHelper; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityCreature; +@@ -197,7 +198,7 @@ public class ActivationRange + maxRange = Math.max( maxRange, waterActivationRange ); + maxRange = Math.max( maxRange, villagerActivationRange ); + // Paper end +- maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange ); ++ maxRange = Math.min( ( ((WorldServer)world).getChunkProvider().playerChunkMap.getEffectiveViewDistance() << 4 ) - 8, maxRange ); // Paper - no-tick view distance + + for ( EntityHuman player : world.getPlayers() ) + { diff --git a/patches/server-unmapped/0001/0472-Add-villager-reputation-API.patch b/patches/server-unmapped/0001/0472-Add-villager-reputation-API.patch new file mode 100644 index 0000000000..007bd7a843 --- /dev/null +++ b/patches/server-unmapped/0001/0472-Add-villager-reputation-API.patch @@ -0,0 +1,153 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 22 Apr 2020 23:29:20 +0200 +Subject: [PATCH] Add villager reputation API + + +diff --git a/src/main/java/com/destroystokyo/paper/entity/villager/ReputationConstructor.java b/src/main/java/com/destroystokyo/paper/entity/villager/ReputationConstructor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0f10c333d88f2e1c56a6c7f22d421084adfd3789 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/villager/ReputationConstructor.java +@@ -0,0 +1,9 @@ ++package com.destroystokyo.paper.entity.villager; ++// Must have own package due to package-level constructor. ++ ++public final class ReputationConstructor { ++ // Abuse the package-level constructor. ++ public static Reputation construct(int[] values) { ++ return new Reputation(values); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java b/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java +index a7f5e4a499c1f6fb1450e536dbf117a8af3b3b84..9cc3a18636a356977577076e96cb7be706c61abf 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java +@@ -27,7 +27,7 @@ import net.minecraft.core.MinecraftSerializableUUID; + + public class Reputation { + +- private final Map a = Maps.newHashMap(); ++ private final Map a = Maps.newHashMap(); public Map getReputations() { return this.a; } // Paper - add getter for reputations + + public Reputation() {} + +@@ -142,11 +142,11 @@ public class Reputation { + return k > reputationtype.h ? Math.max(reputationtype.h, i) : k; + } + +- static class a { ++ public static class a { // Paper - make public + + private final Object2IntMap a; + +- private a() { ++ public a() { // Paper - make public - update CraftVillager setReputation on change + this.a = new Object2IntOpenHashMap(); + } + +@@ -200,6 +200,28 @@ public class Reputation { + public void b(ReputationType reputationtype) { + this.a.removeInt(reputationtype); + } ++ ++ // Paper start - Add villager reputation API ++ private static final com.destroystokyo.paper.entity.villager.ReputationType[] REPUTATION_TYPES = com.destroystokyo.paper.entity.villager.ReputationType.values(); ++ public com.destroystokyo.paper.entity.villager.Reputation getPaperReputation() { ++ int[] reputation = new int[REPUTATION_TYPES.length]; ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_NEGATIVE.ordinal()] = a.getOrDefault(ReputationType.MAJOR_NEGATIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE.ordinal()] = a.getOrDefault(ReputationType.MAJOR_POSITIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE.ordinal()] = a.getOrDefault(ReputationType.MINOR_NEGATIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE.ordinal()] = a.getOrDefault(ReputationType.MINOR_POSITIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.TRADING.ordinal()] = a.getOrDefault(ReputationType.TRADING, 0); ++ return com.destroystokyo.paper.entity.villager.ReputationConstructor.construct(reputation); ++ } ++ ++ public void assignFromPaperReputation(com.destroystokyo.paper.entity.villager.Reputation rep) { ++ int val; ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_NEGATIVE)) != 0) this.a.put(ReputationType.MAJOR_NEGATIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE)) != 0) this.a.put(ReputationType.MAJOR_POSITIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE)) != 0) this.a.put(ReputationType.MINOR_NEGATIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE)) != 0) this.a.put(ReputationType.MINOR_POSITIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.TRADING)) != 0) this.a.put(ReputationType.TRADING, val); ++ } ++ // Paper end + } + + static class b { +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +index c37dd836c284ed8beac6699ec4f5f91886cf3f63..80a9eecfd2eb17db6a7d0b3973f4acdb80f873e8 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +@@ -1031,6 +1031,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + this.bD = 0; + } + ++ public Reputation getReputation() { return this.fj(); } // Paper - OBFHELPER + public Reputation fj() { + return this.by; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +index 4b2451179cdda918808ea7001f5033c7e5a8b9ac..073c4c518be5a32fccd82e5739ede461214007b2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +@@ -17,6 +17,13 @@ import org.bukkit.entity.Villager; + import org.bukkit.entity.Villager.Profession; + import org.bukkit.entity.Villager.Type; + ++// Paper start ++import com.destroystokyo.paper.entity.villager.Reputation; ++import com.google.common.collect.Maps; ++import java.util.Map; ++import java.util.UUID; ++// Paper end ++ + public class CraftVillager extends CraftAbstractVillager implements Villager { + + public CraftVillager(CraftServer server, EntityVillager entity) { +@@ -126,4 +133,45 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { + public static VillagerProfession bukkitToNmsProfession(Profession bukkit) { + return IRegistry.VILLAGER_PROFESSION.get(CraftNamespacedKey.toMinecraft(bukkit.getKey())); + } ++ ++ // Paper start - Add villager reputation API ++ @Override ++ public Reputation getReputation(UUID uniqueId) { ++ net.minecraft.world.entity.ai.gossip.Reputation.a rep = getHandle().getReputation().getReputations().get(uniqueId); ++ if (rep == null) { ++ return new Reputation(Maps.newHashMap()); ++ } ++ ++ return rep.getPaperReputation(); ++ } ++ ++ @Override ++ public Map getReputations() { ++ return getHandle().getReputation().getReputations().entrySet() ++ .stream() ++ .collect(java.util.stream.Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getPaperReputation())); ++ } ++ ++ @Override ++ public void setReputation(UUID uniqueId, Reputation reputation) { ++ net.minecraft.world.entity.ai.gossip.Reputation.a nmsReputation = ++ getHandle().getReputation().getReputations().computeIfAbsent( ++ uniqueId, ++ key -> new net.minecraft.world.entity.ai.gossip.Reputation.a() ++ ); ++ nmsReputation.assignFromPaperReputation(reputation); ++ } ++ ++ @Override ++ public void setReputations(Map reputations) { ++ for (Map.Entry entry : reputations.entrySet()) { ++ setReputation(entry.getKey(), entry.getValue()); ++ } ++ } ++ ++ @Override ++ public void clearReputations() { ++ getHandle().getReputation().getReputations().clear(); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0473-Fix-Light-Command.patch b/patches/server-unmapped/0001/0473-Fix-Light-Command.patch new file mode 100644 index 0000000000..3a0aaf19d2 --- /dev/null +++ b/patches/server-unmapped/0001/0473-Fix-Light-Command.patch @@ -0,0 +1,185 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 7 May 2020 19:17:36 -0400 +Subject: [PATCH] Fix Light Command + +This lets you run /paper fixlight (max 5) to automatically +fix all light data in the chunks. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index eb1e86e8bb0f421e3686ffa02a4015a588107863..d165e8c232c38ba2e2faf93c60c8a127bb74c9b6 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -8,6 +8,8 @@ import com.google.common.collect.ImmutableSet; + import com.google.common.collect.Iterables; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; ++import net.minecraft.core.BlockPosition; ++import net.minecraft.network.protocol.game.PacketPlayOutLightUpdate; + import net.minecraft.resources.MinecraftKey; + import com.google.gson.JsonObject; + import com.google.gson.internal.Streams; +@@ -15,12 +17,14 @@ import com.google.gson.stream.JsonWriter; + import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ChunkProviderServer; ++import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.LightEngineThreaded; + import net.minecraft.server.level.PlayerChunk; + import net.minecraft.server.level.WorldServer; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityTypes; + import net.minecraft.world.level.ChunkCoordIntPair; +-import net.minecraft.server.MCUtil; ++import net.minecraft.world.level.chunk.Chunk; + import org.apache.commons.lang3.tuple.MutablePair; + import org.apache.commons.lang3.tuple.Pair; + import org.bukkit.Bukkit; +@@ -31,6 +35,7 @@ import org.bukkit.command.Command; + import org.bukkit.command.CommandSender; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; + import org.bukkit.entity.Player; + + import java.io.File; +@@ -39,10 +44,12 @@ import java.io.PrintStream; + import java.io.StringWriter; + import java.time.LocalDateTime; + import java.time.format.DateTimeFormatter; ++import java.util.ArrayDeque; + import java.util.ArrayList; + import java.util.Arrays; + import java.util.Collection; + import java.util.Collections; ++import java.util.Deque; + import java.util.Iterator; + import java.util.List; + import java.util.Locale; +@@ -52,7 +59,7 @@ import java.util.stream.Collectors; + + public class PaperCommand extends Command { + private static final String BASE_PERM = "bukkit.command.paper."; +- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo").build(); ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo", "fixlight").build(); + + public PaperCommand(String name) { + super(name); +@@ -173,6 +180,9 @@ public class PaperCommand extends Command { + case "syncloadinfo": + this.doSyncLoadInfo(sender, args); + break; ++ case "fixlight": ++ this.doFixLight(sender, args); ++ break; + case "ver": + if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) + case "version": +@@ -190,6 +200,77 @@ public class PaperCommand extends Command { + return true; + } + ++ private void doFixLight(CommandSender sender, String[] args) { ++ if (!(sender instanceof Player)) { ++ sender.sendMessage("Only players can use this command"); ++ return; ++ } ++ int radius = 2; ++ if (args.length > 1) { ++ try { ++ radius = Math.min(5, Integer.parseInt(args[1])); ++ } catch (Exception e) { ++ sender.sendMessage("Not a number"); ++ return; ++ } ++ ++ } ++ ++ CraftPlayer player = (CraftPlayer) sender; ++ EntityPlayer handle = player.getHandle(); ++ WorldServer world = (WorldServer) handle.world; ++ LightEngineThreaded lightengine = world.getChunkProvider().getLightEngine(); ++ ++ BlockPosition center = MCUtil.toBlockPosition(player.getLocation()); ++ Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); ++ updateLight(sender, world, lightengine, queue); ++ } ++ ++ private void updateLight(CommandSender sender, WorldServer world, LightEngineThreaded lightengine, Deque queue) { ++ ChunkCoordIntPair coord = queue.poll(); ++ if (coord == null) { ++ sender.sendMessage("All Chunks Light updated"); ++ return; ++ } ++ world.getChunkProvider().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> { ++ if (ex != null) { ++ sender.sendMessage("Error loading chunk " + coord); ++ updateLight(sender, world, lightengine, queue); ++ return; ++ } ++ Chunk chunk = (Chunk) either.left().orElse(null); ++ if (chunk == null) { ++ updateLight(sender, world, lightengine, queue); ++ return; ++ } ++ lightengine.a(world.paperConfig.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue ++ sender.sendMessage("Updating Light " + coord); ++ int cx = chunk.getPos().x << 4; ++ int cz = chunk.getPos().z << 4; ++ for (int y = 0; y < world.getHeight(); y++) { ++ for (int x = 0; x < 16; x++) { ++ for (int z = 0; z < 16; z++) { ++ BlockPosition pos = new BlockPosition(cx + x, y, cz + z); ++ lightengine.a(pos); ++ } ++ } ++ } ++ lightengine.queueUpdate(); ++ PlayerChunk visibleChunk = world.getChunkProvider().playerChunkMap.getVisibleChunk(chunk.coordinateKey); ++ if (visibleChunk != null) { ++ world.getChunkProvider().playerChunkMap.addLightTask(visibleChunk, () -> { ++ MinecraftServer.getServer().processQueue.add(() -> { ++ visibleChunk.sendPacketToTrackedPlayers(new PacketPlayOutLightUpdate(chunk.getPos(), lightengine, true), false); ++ updateLight(sender, world, lightengine, queue); ++ }); ++ }); ++ } else { ++ updateLight(sender, world, lightengine, queue); ++ } ++ lightengine.a(world.paperConfig.lightQueueSize); ++ }, MinecraftServer.getServer()); ++ } ++ + private void doSyncLoadInfo(CommandSender sender, String[] args) { + if (!SyncLoadFinder.ENABLED) { + sender.sendMessage(ChatColor.RED + "This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set."); +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 1f6333c2c26ad04e23d2881235ed1dcf707be038..e53054fc46e528f9c713eb4c03add61316e19396 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -350,6 +350,7 @@ public class PlayerChunk { + + } + ++ public void sendPacketToTrackedPlayers(Packet packet, boolean flag) { a(packet, flag); } // Paper - OBFHELPER + private void a(Packet packet, boolean flag) { + // Paper start - per player view distance + // there can be potential desync with player's last mapped section and the view distance map, so use the +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 762598b1dc8c6fb4beaad01e5777d0a950845eaf..5538d97e237e448a7d3eb76a57609980c3a6bddb 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -346,11 +346,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + Mailbox mailbox = Mailbox.a("main", iasynctaskhandler::a); + + this.worldLoadListener = worldloadlistener; +- ThreadedMailbox threadedmailbox1 = ThreadedMailbox.a(executor, "light"); ++ ThreadedMailbox lightthreaded; ThreadedMailbox threadedmailbox1 = lightthreaded = ThreadedMailbox.a(executor, "light"); // Paper + + this.p = new ChunkTaskQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE); + this.mailboxWorldGen = this.p.a(threadedmailbox, false); + this.mailboxMain = this.p.a(mailbox, false); ++ this.mailboxLight = this.p.a(lightthreaded, false);// Paper + this.lightEngine = new LightEngineThreaded(ilightaccess, this, this.world.getDimensionManager().hasSkyLight(), threadedmailbox1, this.p.a(threadedmailbox1, false)); + this.chunkDistanceManager = new PlayerChunkMap.a(executor, iasynctaskhandler); this.chunkDistanceManager.chunkMap = this; // Paper + this.l = supplier; diff --git a/patches/server-unmapped/0001/0474-Fix-PotionEffect-ignores-icon-flag.patch b/patches/server-unmapped/0001/0474-Fix-PotionEffect-ignores-icon-flag.patch new file mode 100644 index 0000000000..790460d707 --- /dev/null +++ b/patches/server-unmapped/0001/0474-Fix-PotionEffect-ignores-icon-flag.patch @@ -0,0 +1,19 @@ +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 + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index b18292ef2e00b4ef8a0b2da5f63a596dbd04b1fd..5563e7c1ecc9e607ba0be21ae16a544b24d6f030 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -420,7 +420,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + + @Override + public boolean addPotionEffect(PotionEffect effect, boolean force) { +- getHandle().addEffect(new MobEffect(MobEffectList.fromId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()), EntityPotionEffectEvent.Cause.PLUGIN); ++ getHandle().addEffect(new MobEffect(MobEffectList.fromId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon + return true; + } + diff --git a/patches/server-unmapped/0001/0475-Optimize-brigadier-child-sorting-performance.patch b/patches/server-unmapped/0001/0475-Optimize-brigadier-child-sorting-performance.patch new file mode 100644 index 0000000000..e32f00ecf8 --- /dev/null +++ b/patches/server-unmapped/0001/0475-Optimize-brigadier-child-sorting-performance.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: virustotalop +Date: Thu, 16 Apr 2020 20:51:32 -0700 +Subject: [PATCH] Optimize brigadier child sorting performance + + +diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +index f0f25fa40d4a0aa0e299ad11847b6a5f9102c214..7ef6c99d2235eed38197aa76bc9553d7efbe52a4 100644 +--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +@@ -27,7 +27,7 @@ import java.util.stream.Collectors; + import net.minecraft.commands.CommandListenerWrapper; // CraftBukkit + + public abstract class CommandNode implements Comparable> { +- private Map> children = Maps.newLinkedHashMap(); ++ private Map> children = Maps.newTreeMap(); //Paper - Switch to tree map for automatic sorting + private Map> literals = Maps.newLinkedHashMap(); + private Map> arguments = Maps.newLinkedHashMap(); + private final Predicate requirement; +@@ -107,8 +107,7 @@ public abstract class CommandNode implements Comparable> { + arguments.put(node.getName(), (ArgumentCommandNode) node); + } + } +- +- children = children.entrySet().stream().sorted(Map.Entry.comparingByValue()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); ++ //Paper - Remove manual sorting, it is no longer needed + } + + public void findAmbiguities(final AmbiguityConsumer consumer) { diff --git a/patches/server-unmapped/0001/0476-Potential-bed-API.patch b/patches/server-unmapped/0001/0476-Potential-bed-API.patch new file mode 100644 index 0000000000..f002048a71 --- /dev/null +++ b/patches/server-unmapped/0001/0476-Potential-bed-API.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Sun, 10 May 2020 23:06:30 -0400 +Subject: [PATCH] Potential bed API + +Adds a new method to fetch the location of a player's bed without generating any sync loads. + +getPotentialBedLocation - Gets the last known location of a player's bed. This does not preform any check if the bed is still valid and does not load any chunks. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 5b142e96248278c6bb6068879bb5ad1578b0f79f..92501a415813b3b0f2be492a4711962320264a76 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -13,6 +13,7 @@ import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.network.protocol.game.PacketPlayInCloseWindow; + import net.minecraft.network.protocol.game.PacketPlayOutOpenWindow; + import net.minecraft.server.level.EntityPlayer; ++import net.minecraft.server.level.WorldServer; + import net.minecraft.world.ITileInventory; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityTypes; +@@ -127,6 +128,22 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + return getHandle().sleepTicks; + } + ++ // Paper start - Potential bed api ++ @Override ++ public Location getPotentialBedLocation() { ++ EntityPlayer handle = (EntityPlayer) getHandle(); ++ BlockPosition bed = handle.getSpawn(); ++ if (bed == null) { ++ return null; ++ } ++ ++ WorldServer worldServer = handle.server.getWorldServer(handle.getSpawnDimension()); ++ if (worldServer == null) { ++ return null; ++ } ++ return new Location(worldServer.getWorld(), bed.getX(), bed.getY(), bed.getZ()); ++ } ++ // Paper end + @Override + public boolean sleep(Location location, boolean force) { + Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server-unmapped/0001/0477-Wait-for-Async-Tasks-during-shutdown.patch b/patches/server-unmapped/0001/0477-Wait-for-Async-Tasks-during-shutdown.patch new file mode 100644 index 0000000000..e9b6d87ebb --- /dev/null +++ b/patches/server-unmapped/0001/0477-Wait-for-Async-Tasks-during-shutdown.patch @@ -0,0 +1,63 @@ +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 25cb877dc3879ff5a1bfaf616ba9942f951eba10..5b9f03f3118e6d19ed5c3d41a94b06172d594a81 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -892,6 +892,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0) { ++ try { ++ Thread.sleep(100); ++ } catch (InterruptedException e) {} ++ pollCount++; ++ } ++ ++ List overdueWorkers = getScheduler().getActiveWorkers(); ++ for (BukkitWorker worker : overdueWorkers) { ++ Plugin plugin = worker.getOwner(); ++ String author = ""; ++ if (plugin.getDescription().getAuthors().size() > 0) { ++ author = plugin.getDescription().getAuthors().get(0); ++ } ++ getLogger().log(Level.SEVERE, String.format( ++ "Nag author: '%s' of '%s' about the following: %s", ++ author, ++ plugin.getDescription().getName(), ++ "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 ++ + @Override + public void reloadData() { + CommandReload.reload(console); diff --git a/patches/server-unmapped/0001/0478-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch b/patches/server-unmapped/0001/0478-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch new file mode 100644 index 0000000000..a5eab26a70 --- /dev/null +++ b/patches/server-unmapped/0001/0478-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Sat, 9 May 2020 02:01:48 -0400 +Subject: [PATCH] Ensure EntityRaider respects game and entity rules for + picking up items + + +diff --git a/src/main/java/net/minecraft/world/entity/raid/EntityRaider.java b/src/main/java/net/minecraft/world/entity/raid/EntityRaider.java +index b9f5ed4e9348648a248dcd23eb100ceac49f26df..ff41ee884e3e46af1b1e9fb550f0abc6998fd031 100644 +--- a/src/main/java/net/minecraft/world/entity/raid/EntityRaider.java ++++ b/src/main/java/net/minecraft/world/entity/raid/EntityRaider.java +@@ -523,7 +523,7 @@ public abstract class EntityRaider extends EntityMonsterPatrolling { + + public class b extends PathfinderGoal { + +- private final T b; ++ private final T b; private T getRaider() { return b; } // Paper - obfhelper + + public b(T entityraider) { // CraftBukkit - decompile error + this.b = entityraider; +@@ -532,6 +532,7 @@ public abstract class EntityRaider extends EntityMonsterPatrolling { + + @Override + public boolean a() { ++ if (!getRaider().world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) || !getRaider().canPickupLoot()) return false; // Paper - respect game and entity rules for picking up items + Raid raid = this.b.fa(); + + if (this.b.fb() && !this.b.fa().a() && this.b.eN() && !ItemStack.matches(this.b.getEquipment(EnumItemSlot.HEAD), Raid.s())) { diff --git a/patches/server-unmapped/0001/0479-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch b/patches/server-unmapped/0001/0479-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch new file mode 100644 index 0000000000..c5742080fb --- /dev/null +++ b/patches/server-unmapped/0001/0479-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch @@ -0,0 +1,173 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 78271b400c79578d043b20a5389a37b1bef9a70d..5f3b0d95cc7e6a0434d78ea7305a70689c41c71c 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -416,4 +416,17 @@ public class PaperConfig { + private static void midTickChunkTasks() { + midTickChunkTasks = getInt("settings.chunk-tasks-per-tick", midTickChunkTasks); + } ++ ++ public static boolean allowBlockPermanentBreakingExploits = false; ++ private static void allowBlockPermanentBreakingExploits() { ++ if (config.contains("allow-perm-block-break-exploits")) { ++ allowBlockPermanentBreakingExploits = config.getBoolean("allow-perm-block-break-exploits", false); ++ config.set("allow-perm-block-break-exploits", null); ++ } ++ ++ config.set("settings.unsupported-settings.allow-permanent-block-break-exploits-readme", "This setting controls if players should be able to break bedrock, end portals and other intended to be permanent blocks."); ++ allowBlockPermanentBreakingExploits = getBoolean("settings.unsupported-settings.allow-permanent-block-break-exploits", allowBlockPermanentBreakingExploits); ++ ++ } ++ + } +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index a9ecc2b4da587ca3d3c99f8c8af38092a02fb572..0b3479aae8f7cad7bd0b8b64aa2dead43baf4c56 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -153,6 +153,7 @@ public class Explosion { + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPosition blockposition = new BlockPosition(d4, d5, d6); + IBlockData iblockdata = this.world.getType(blockposition); ++ if (!iblockdata.isDestroyable()) continue; // Paper + Fluid fluid = iblockdata.getFluid(); // Paper + Optional optional = this.l.a(this, this.world, blockposition, iblockdata, fluid); + +@@ -306,7 +307,7 @@ public class Explosion { + IBlockData iblockdata = this.world.getType(blockposition); + Block block = iblockdata.getBlock(); + +- if (!iblockdata.isAir()) { ++ if (!iblockdata.isAir() && iblockdata.isDestroyable()) { // Paper + BlockPosition blockposition1 = blockposition.immutableCopy(); + + this.world.getMethodProfiler().enter("explosion_blocks"); +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 7ead848342bfbb5b20e95d716805f4b4fd36eb63..9369a0c6c0ae2d8518ebfb17f2c93ead2647ab8d 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -422,6 +422,10 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public boolean a(BlockPosition blockposition, IBlockData iblockdata, int i, int j) { + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { ++ // Paper start ++ IBlockData type = getType(blockposition); ++ if (!type.isDestroyable()) return false; ++ // Paper end + CraftBlockState blockstate = capturedBlockStates.get(blockposition); + if (blockstate == null) { + blockstate = CapturedBlockState.getTreeBlockState(this, blockposition, i); +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 e5c43b383a93fac76333a67b41535ab009d1dcf3..cc512bd2e89382e7fdbc59b41640e95ccafbbfe9 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -62,6 +62,19 @@ public class Block extends BlockBase implements IMaterial { + protected final BlockStateList blockStateList; + private IBlockData blockData; + // Paper start ++ public final boolean isDestroyable() { ++ return com.destroystokyo.paper.PaperConfig.allowBlockPermanentBreakingExploits || ++ 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/BlockPiston.java b/src/main/java/net/minecraft/world/level/block/piston/BlockPiston.java +index 7de86d6232eb84642fb6423a1b0a9f30d9df9f2b..e062fd288098127fae22a55562e0207ceaf50163 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/BlockPiston.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/BlockPiston.java +@@ -194,6 +194,12 @@ public class BlockPiston extends BlockDirectional { + @Override + public boolean a(IBlockData iblockdata, World world, BlockPosition blockposition, int i, int j) { + EnumDirection enumdirection = (EnumDirection) iblockdata.get(BlockPiston.FACING); ++ // Paper start - prevent retracting when we're facing the wrong way (we were replaced before retraction could occur) ++ EnumDirection directionQueuedAs = EnumDirection.fromType1(j & 7); // Paper - copied from below ++ if (!com.destroystokyo.paper.PaperConfig.allowBlockPermanentBreakingExploits && enumdirection != directionQueuedAs) { ++ return false; ++ } ++ // Paper end - prevent retracting when we're facing the wrong way + + if (!world.isClientSide) { + boolean flag = this.a(world, blockposition, enumdirection); +@@ -225,7 +231,7 @@ public class BlockPiston extends BlockDirectional { + IBlockData iblockdata1 = (IBlockData) ((IBlockData) Blocks.MOVING_PISTON.getBlockData().set(BlockPistonMoving.a, enumdirection)).set(BlockPistonMoving.b, this.sticky ? BlockPropertyPistonType.STICKY : BlockPropertyPistonType.DEFAULT); + + world.setTypeAndData(blockposition, iblockdata1, 20); +- world.setTileEntity(blockposition, BlockPistonMoving.a((IBlockData) this.getBlockData().set(BlockPiston.FACING, EnumDirection.fromType1(j & 7)), enumdirection, false, true)); ++ world.setTileEntity(blockposition, BlockPistonMoving.a((IBlockData) this.getBlockData().set(BlockPiston.FACING, EnumDirection.fromType1(j & 7)), enumdirection, false, true)); // Paper - diff on change, j is facing direction - copy this above + world.update(blockposition, iblockdata1.getBlock()); + iblockdata1.a(world, blockposition, 2); + if (this.sticky) { +@@ -254,7 +260,14 @@ public class BlockPiston extends BlockDirectional { + } + } + } else { +- world.a(blockposition.shift(enumdirection), false); ++ // Paper start - fix headless pistons breaking blocks ++ BlockPosition headPos = blockposition.shift(enumdirection); ++ if (com.destroystokyo.paper.PaperConfig.allowBlockPermanentBreakingExploits || world.getType(headPos) == Blocks.PISTON_HEAD.getBlockData().set(FACING, enumdirection)) { // double check to make sure we're not a headless piston. ++ world.setAir(headPos, false); ++ } else { ++ ((WorldServer)world).getChunkProvider().flagDirty(headPos); // ... fix client desync ++ } ++ // Paper end - fix headless pistons breaking blocks + } + + world.playSound((EntityHuman) null, blockposition, SoundEffects.BLOCK_PISTON_CONTRACT, SoundCategory.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F); +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +index 57857cc33603cf278de424b540a3d4a5943584c9..2a785ea58a7bdc80c703a60bc6ed602dc8040aa0 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +@@ -190,7 +190,7 @@ public abstract class BlockBase { + + @Deprecated + public boolean a(IBlockData iblockdata, BlockActionContext blockactioncontext) { +- return this.material.isReplaceable() && (blockactioncontext.getItemStack().isEmpty() || blockactioncontext.getItemStack().getItem() != this.getItem()); ++ return this.material.isReplaceable() && (blockactioncontext.getItemStack().isEmpty() || blockactioncontext.getItemStack().getItem() != this.getItem()) && (iblockdata.isDestroyable() || (blockactioncontext.getEntity() != null && blockactioncontext.getEntity().abilities.canInstantlyBuild)); // Paper + } + + @Deprecated +@@ -394,7 +394,11 @@ public abstract class BlockBase { + public Block getBlock() { + return (Block) this.c; + } +- ++ // Paper start ++ public final boolean isDestroyable() { ++ return getBlock().isDestroyable(); ++ } ++ // Paper end + public Material getMaterial() { + return this.g; + } +@@ -484,7 +488,7 @@ public abstract class BlockBase { + } + + public EnumPistonReaction getPushReaction() { +- return this.getBlock().getPushReaction(this.p()); ++ return !isDestroyable() ? EnumPistonReaction.BLOCK : this.getBlock().getPushReaction(this.p()); // Paper + } + + public boolean i(IBlockAccess iblockaccess, BlockPosition blockposition) { diff --git a/patches/server-unmapped/0001/0480-Optimize-NibbleArray-to-use-pooled-buffers.patch b/patches/server-unmapped/0001/0480-Optimize-NibbleArray-to-use-pooled-buffers.patch new file mode 100644 index 0000000000..0b519eafdf --- /dev/null +++ b/patches/server-unmapped/0001/0480-Optimize-NibbleArray-to-use-pooled-buffers.patch @@ -0,0 +1,394 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 6 May 2020 23:30:30 -0400 +Subject: [PATCH] Optimize NibbleArray to use pooled buffers + +Massively reduces memory allocation of 2048 byte buffers by using +an object pool for these. + +Uses lots of advanced new capabilities of the Paper codebase :) + +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutLightUpdate.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutLightUpdate.java +index 247d969e7d1aa59d9650fce1032aaa09db3903e5..9050ff7180f63c1f5756570446c4d0a8cc767779 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutLightUpdate.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutLightUpdate.java +@@ -1,12 +1,16 @@ + package net.minecraft.network.protocol.game; + + import com.google.common.collect.Lists; ++import io.netty.channel.ChannelFuture; // Paper ++ + import java.io.IOException; + import java.util.Iterator; + import java.util.List; + import net.minecraft.core.SectionPosition; + import net.minecraft.network.PacketDataSerializer; + import net.minecraft.network.protocol.Packet; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.EnumSkyBlock; + import net.minecraft.world.level.chunk.NibbleArray; +@@ -24,14 +28,43 @@ public class PacketPlayOutLightUpdate implements Packet { + private List h; + private boolean i; + ++ // Paper start ++ java.lang.Runnable cleaner1; ++ java.lang.Runnable cleaner2; ++ java.util.concurrent.atomic.AtomicInteger remainingSends = new java.util.concurrent.atomic.AtomicInteger(0); ++ ++ @Override ++ public void onPacketDispatch(EntityPlayer player) { ++ remainingSends.incrementAndGet(); ++ } ++ ++ @Override ++ public void onPacketDispatchFinish(EntityPlayer player, ChannelFuture future) { ++ if (remainingSends.decrementAndGet() <= 0) { ++ // incase of any race conditions, schedule this delayed ++ MCUtil.scheduleTask(5, () -> { ++ if (remainingSends.get() == 0) { ++ cleaner1.run(); ++ cleaner2.run(); ++ } ++ }, "Light Packet Release"); ++ } ++ } ++ ++ @Override ++ public boolean hasFinishListener() { ++ return true; ++ } ++ ++ // Paper end + public PacketPlayOutLightUpdate() {} + + public PacketPlayOutLightUpdate(ChunkCoordIntPair chunkcoordintpair, LightEngine lightengine, boolean flag) { + this.a = chunkcoordintpair.x; + this.b = chunkcoordintpair.z; + this.i = flag; +- this.g = Lists.newArrayList(); +- this.h = Lists.newArrayList(); ++ this.g = Lists.newArrayList();cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper ++ this.h = Lists.newArrayList();cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper + + for (int i = 0; i < 18; ++i) { + NibbleArray nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + i)); +@@ -42,7 +75,7 @@ public class PacketPlayOutLightUpdate implements Packet { + this.e |= 1 << i; + } else { + this.c |= 1 << i; +- this.g.add(nibblearray.asBytes().clone()); ++ this.g.add(nibblearray.getCloneIfSet()); // Paper + } + } + +@@ -51,7 +84,7 @@ public class PacketPlayOutLightUpdate implements Packet { + this.f |= 1 << i; + } else { + this.d |= 1 << i; +- this.h.add(nibblearray1.asBytes().clone()); ++ this.h.add(nibblearray1.getCloneIfSet()); // Paper + } + } + } +@@ -64,8 +97,8 @@ public class PacketPlayOutLightUpdate implements Packet { + this.i = flag; + this.c = i; + this.d = j; +- this.g = Lists.newArrayList(); +- this.h = Lists.newArrayList(); ++ this.g = Lists.newArrayList();cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper ++ this.h = Lists.newArrayList();cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper + + for (int k = 0; k < 18; ++k) { + NibbleArray nibblearray; +@@ -73,7 +106,7 @@ public class PacketPlayOutLightUpdate implements Packet { + if ((this.c & 1 << k) != 0) { + nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + k)); + if (nibblearray != null && !nibblearray.c()) { +- this.g.add(nibblearray.asBytes().clone()); ++ this.g.add(nibblearray.getCloneIfSet()); // Paper + } else { + this.c &= ~(1 << k); + if (nibblearray != null) { +@@ -85,7 +118,7 @@ public class PacketPlayOutLightUpdate implements Packet { + if ((this.d & 1 << k) != 0) { + nibblearray = lightengine.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, -1 + k)); + if (nibblearray != null && !nibblearray.c()) { +- this.h.add(nibblearray.asBytes().clone()); ++ this.h.add(nibblearray.getCloneIfSet()); // Paper + } else { + this.d &= ~(1 << k); + if (nibblearray != null) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java b/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java +index 86b4db483787c5fd10461f7d7e90a772ee049599..b82420e9a5d42a4383d24921614fe613c640edb9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java ++++ b/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java +@@ -1,18 +1,78 @@ + // mc-dev import + package net.minecraft.world.level.chunk; + ++import com.destroystokyo.paper.util.pooled.PooledObjects; // Paper ++ ++import javax.annotation.Nonnull; + import javax.annotation.Nullable; + import net.minecraft.SystemUtils; ++import net.minecraft.server.MCUtil; + + public class NibbleArray { + +- @Nullable +- protected byte[] a; ++ // Paper start ++ public static byte[] EMPTY_NIBBLE = new byte[2048]; ++ private static final int nibbleBucketSizeMultiplier = Integer.getInteger("Paper.nibbleBucketSize", 3072); ++ private static final int maxPoolSize = Integer.getInteger("Paper.maxNibblePoolSize", (int) Math.min(6, Math.max(1, Runtime.getRuntime().maxMemory() / 1024 / 1024 / 1024)) * (nibbleBucketSizeMultiplier * 8)); ++ public static final PooledObjects BYTE_2048 = new PooledObjects<>(() -> new byte[2048], maxPoolSize); ++ public static void releaseBytes(byte[] bytes) { ++ if (bytes != null && bytes != EMPTY_NIBBLE && bytes.length == 2048) { ++ System.arraycopy(EMPTY_NIBBLE, 0, bytes, 0, 2048); ++ BYTE_2048.release(bytes); ++ } ++ } ++ ++ public NibbleArray markPoolSafe(byte[] bytes) { ++ if (bytes != EMPTY_NIBBLE) this.a = bytes; ++ return markPoolSafe(); ++ } ++ public NibbleArray markPoolSafe() { ++ poolSafe = true; ++ return this; ++ } ++ public byte[] getIfSet() { ++ return this.a != null ? this.a : EMPTY_NIBBLE; ++ } ++ public byte[] getCloneIfSet() { ++ if (a == null) { ++ return EMPTY_NIBBLE; ++ } ++ byte[] ret = BYTE_2048.acquire(); ++ System.arraycopy(getIfSet(), 0, ret, 0, 2048); ++ return ret; ++ } ++ ++ public NibbleArray cloneAndSet(byte[] bytes) { ++ if (bytes != null && bytes != EMPTY_NIBBLE) { ++ this.a = BYTE_2048.acquire(); ++ System.arraycopy(bytes, 0, this.a, 0, 2048); ++ } ++ return this; ++ } ++ boolean poolSafe = false; ++ public java.lang.Runnable cleaner; ++ private void registerCleaner() { ++ if (!poolSafe) { ++ cleaner = MCUtil.registerCleaner(this, this.a, NibbleArray::releaseBytes); ++ } else { ++ cleaner = MCUtil.once(() -> NibbleArray.releaseBytes(this.a)); ++ } ++ } ++ // Paper end ++ @Nullable protected byte[] a; ++ + + public NibbleArray() {} + + public NibbleArray(byte[] abyte) { ++ // Paper start ++ this(abyte, false); ++ } ++ public NibbleArray(byte[] abyte, boolean isSafe) { + this.a = abyte; ++ if (!isSafe) this.a = getCloneIfSet(); // Paper - clone for safety ++ registerCleaner(); ++ // Paper end + if (abyte.length != 2048) { + throw (IllegalArgumentException) SystemUtils.c((Throwable) (new IllegalArgumentException("ChunkNibbleArrays should be 2048 bytes not: " + abyte.length))); + } +@@ -46,7 +106,8 @@ public class NibbleArray { + + public void a(int i, int j) { // PAIL: private -> public + if (this.a == null) { +- this.a = new byte[2048]; ++ this.a = BYTE_2048.acquire(); // Paper ++ registerCleaner();// Paper + } + + int k = this.d(i); +@@ -68,14 +129,36 @@ public class NibbleArray { + public byte[] asBytes() { + if (this.a == null) { + this.a = new byte[2048]; ++ } else { // Paper start ++ // Accessor may need this object past garbage collection so need to clone it and return pooled value ++ // If we know its safe for pre GC access, use asBytesPoolSafe(). If you just need read, use getIfSet() ++ Runnable cleaner = this.cleaner; ++ if (cleaner != null) { ++ this.a = this.a.clone(); ++ cleaner.run(); // release the previously pooled value ++ this.cleaner = null; ++ } ++ } ++ // Paper end ++ ++ return this.a; ++ } ++ ++ @Nonnull ++ public byte[] asBytesPoolSafe() { ++ if (this.a == null) { ++ this.a = BYTE_2048.acquire(); // Paper ++ registerCleaner(); // Paper + } + ++ //noinspection ConstantConditions + return this.a; + } ++ // Paper end + + public NibbleArray copy() { return this.b(); } // Paper - OBFHELPER + public NibbleArray b() { +- return this.a == null ? new NibbleArray() : new NibbleArray((byte[]) this.a.clone()); ++ return this.a == null ? new NibbleArray() : new NibbleArray(this.a); // Paper - clone in ctor + } + + public String toString() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +index e16e046d165330326ed220c9c440a637007f3137..91bcbf7156dd90b00e2d53bb6bff4abc44ecb721 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +@@ -435,11 +435,11 @@ public class ChunkRegionLoader { + } + + if (nibblearray != null && !nibblearray.c()) { +- nbttagcompound2.setByteArray("BlockLight", nibblearray.asBytes()); ++ nbttagcompound2.setByteArray("BlockLight", nibblearray.asBytesPoolSafe().clone()); // Paper + } + + if (nibblearray1 != null && !nibblearray1.c()) { +- nbttagcompound2.setByteArray("SkyLight", nibblearray1.asBytes()); ++ nbttagcompound2.setByteArray("SkyLight", nibblearray1.asBytesPoolSafe().clone()); // Paper + } + + nbttaglist.add(nbttagcompound2); +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java +index 5b1ff4ff87591dd4ff0b79e4ac6ff0494fc3d0f8..9ba9efb181b9607f25b7c921e69e4c59b182d429 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorage.java +@@ -156,7 +156,7 @@ public abstract class LightEngineStorage> e + protected NibbleArray j(long i) { + NibbleArray nibblearray = (NibbleArray) this.i.get(i); + +- return nibblearray != null ? nibblearray : new NibbleArray(); ++ return nibblearray != null ? nibblearray : new NibbleArray().markPoolSafe(); // Paper + } + + protected void a(LightEngineLayer lightenginelayer, long i) { +@@ -338,12 +338,12 @@ public abstract class LightEngineStorage> e + + protected void a(long i, @Nullable NibbleArray nibblearray, boolean flag) { + if (nibblearray != null) { +- this.i.put(i, nibblearray); ++ NibbleArray remove = this.i.put(i, nibblearray); if (remove != null && remove.cleaner != null) remove.cleaner.run(); // Paper - clean up when removed + if (!flag) { + this.n.add(i); + } + } else { +- this.i.remove(i); ++ NibbleArray remove = this.i.remove(i); if (remove != null && remove.cleaner != null) remove.cleaner.run(); // Paper - clean up when removed + } + + } +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java +index ed7864c552054fc47c6010a094230ce4aebf1c54..da78d4c4b5f8af4648ac82d63c21f6a2a5b73ecb 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java +@@ -2,6 +2,7 @@ package net.minecraft.world.level.lighting; + + import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + import javax.annotation.Nullable; ++import net.minecraft.server.MCUtil; + import net.minecraft.world.level.chunk.NibbleArray; + + public abstract class LightEngineStorageArray> { +@@ -34,7 +35,9 @@ public abstract class LightEngineStorageArray +Date: Mon, 27 Apr 2020 02:48:06 -0700 +Subject: [PATCH] Reduce MutableInt allocations from light engine + +We can abuse the fact light is single threaded and share an instance +per light engine instance + +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineBlock.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineBlock.java +index 2a0a5a70c795ba33780c8db774eaf9769a85daa7..f6198069e3ca421b4f551939263c7cf8bd5b754e 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineBlock.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineBlock.java +@@ -16,6 +16,7 @@ public final class LightEngineBlock extends LightEngineLayer= 15) { +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineSky.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineSky.java +index 113c575a16121aa1146f21a6f41ebd9d12a0c924..37fa5faea6e2972e3eb8a3cbd1913ef38dc9456f 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineSky.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineSky.java +@@ -15,6 +15,7 @@ public final class LightEngineSky extends LightEngineLayer= 15) { + return k; + } else { +- MutableInt mutableint = new MutableInt(); ++ //MutableInt mutableint = new MutableInt(); // Paper - share mutableint, single threaded + IBlockData iblockdata = this.a(j, mutableint); + + if (mutableint.getValue() >= 15) { diff --git a/patches/server-unmapped/0001/0482-Reduce-allocation-of-Vec3D-by-entity-tracker.patch b/patches/server-unmapped/0001/0482-Reduce-allocation-of-Vec3D-by-entity-tracker.patch new file mode 100644 index 0000000000..a02030cca4 --- /dev/null +++ b/patches/server-unmapped/0001/0482-Reduce-allocation-of-Vec3D-by-entity-tracker.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 27 Apr 2020 00:04:16 -0700 +Subject: [PATCH] Reduce allocation of Vec3D by entity tracker + + +diff --git a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java +index b64aa6c9ce906b08e43891f8c465fa4e8b2a8906..58dd349adf2bc9bac6569464ef7a7aec81729e79 100644 +--- a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java ++++ b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java +@@ -155,8 +155,12 @@ public class EntityTrackerEntry { + ++this.o; + i = MathHelper.d(this.tracker.yaw * 256.0F / 360.0F); + j = MathHelper.d(this.tracker.pitch * 256.0F / 360.0F); +- Vec3D vec3d = this.tracker.getPositionVector().d(PacketPlayOutEntity.a(this.xLoc, this.yLoc, this.zLoc)); +- boolean flag1 = vec3d.g() >= 7.62939453125E-6D; ++ // Paper start - reduce allocation of Vec3D here ++ double vec3d_dx = this.tracker.locX() - 2.44140625E-4D*(this.xLoc); ++ double vec3d_dy = this.tracker.locY() - 2.44140625E-4D*(this.yLoc); ++ double vec3d_dz = this.tracker.locZ() - 2.44140625E-4D*(this.zLoc); ++ boolean flag1 = (vec3d_dx * vec3d_dx + vec3d_dy * vec3d_dy + vec3d_dz * vec3d_dz) >= 7.62939453125E-6D; ++ // Paper end - reduce allocation of Vec3D here + Packet packet1 = null; + boolean flag2 = flag1 || this.tickCounter % 60 == 0; + boolean flag3 = Math.abs(i - this.yRot) >= 1 || Math.abs(j - this.xRot) >= 1; +@@ -173,9 +177,11 @@ public class EntityTrackerEntry { + // CraftBukkit end + + if (this.tickCounter > 0 || this.tracker instanceof EntityArrow) { +- long k = PacketPlayOutEntity.a(vec3d.x); +- long l = PacketPlayOutEntity.a(vec3d.y); +- long i1 = PacketPlayOutEntity.a(vec3d.z); ++ // Paper start - remove allocation of Vec3D here ++ long k = PacketPlayOutEntity.a(vec3d_dx); ++ long l = PacketPlayOutEntity.a(vec3d_dy); ++ long i1 = PacketPlayOutEntity.a(vec3d_dz); ++ // Paper end - remove allocation of Vec3D here + boolean flag4 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L; + + if (!flag4 && this.o <= 400 && !this.q && this.r == this.tracker.isOnGround()) { +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 5538d97e237e448a7d3eb76a57609980c3a6bddb..ede47aaaace80280756fe4463def1ea26792c9e4 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -2227,9 +2227,14 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially + public void updatePlayer(EntityPlayer entityplayer) { + org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot + if (entityplayer != this.tracker) { +- Vec3D vec3d = entityplayer.getPositionVector().d(this.tracker.getPositionVector()); // MC-155077, SPIGOT-5113 ++ // Paper start - remove allocation of Vec3D here ++ //Vec3D vec3d = entityplayer.getPositionVector().d(this.tracker.getPositionVector()); // MC-155077, SPIGOT-5113 ++ double vec3d_dx = entityplayer.locX() - this.tracker.locX(); ++ double vec3d_dy = entityplayer.locY() - this.tracker.locY(); ++ double vec3d_dz = entityplayer.locZ() - this.tracker.locZ(); ++ // Paper end - remove allocation of Vec3D here + int i = Math.min(this.b(), (PlayerChunkMap.this.viewDistance - 1) * 16); +- boolean flag = vec3d.x >= (double) (-i) && vec3d.x <= (double) i && vec3d.z >= (double) (-i) && vec3d.z <= (double) i && this.tracker.a(entityplayer); ++ boolean flag = vec3d_dx >= (double) (-i) && vec3d_dx <= (double) i && vec3d_dz >= (double) (-i) && vec3d_dz <= (double) i && this.tracker.a(entityplayer); // Paper - remove allocation of Vec3D here + + if (flag) { + boolean flag1 = this.tracker.attachedToPlayer; diff --git a/patches/server-unmapped/0001/0483-Ensure-safe-gateway-teleport.patch b/patches/server-unmapped/0001/0483-Ensure-safe-gateway-teleport.patch new file mode 100644 index 0000000000..cf98a288ea --- /dev/null +++ b/patches/server-unmapped/0001/0483-Ensure-safe-gateway-teleport.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Fri, 15 May 2020 01:10:03 -0400 +Subject: [PATCH] Ensure safe gateway teleport + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java +index b7548d0b3938d95328fc86db4000190532eaa8f5..855c49164277ca96ca08fb204d851a5ad6789990 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityEndGateway.java +@@ -89,9 +89,14 @@ public class TileEntityEndGateway extends TileEntityEnderPortal implements ITick + } else if (!this.world.isClientSide) { + List list = this.world.a(Entity.class, new AxisAlignedBB(this.getPosition()), TileEntityEndGateway::a); + +- if (!list.isEmpty()) { +- this.b((Entity) list.get(this.world.random.nextInt(list.size()))); ++ // Paper start ++ for (Entity entity : list) { ++ if (entity.canPortal()) { ++ this.b(entity); ++ break; ++ } + } ++ // Paper end + + if (this.age % 2400L == 0L) { + this.h(); diff --git a/patches/server-unmapped/0001/0484-Add-option-for-console-having-all-permissions.patch b/patches/server-unmapped/0001/0484-Add-option-for-console-having-all-permissions.patch new file mode 100644 index 0000000000..908755e004 --- /dev/null +++ b/patches/server-unmapped/0001/0484-Add-option-for-console-having-all-permissions.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 16 May 2020 10:12:15 +0200 +Subject: [PATCH] Add option for console having all permissions + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 5f3b0d95cc7e6a0434d78ea7305a70689c41c71c..7f140333c2e62012fa572c1a061d84432426997f 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -429,4 +429,9 @@ public class PaperConfig { + + } + ++ public static boolean consoleHasAllPermissions = false; ++ private static void consoleHasAllPermissions() { ++ consoleHasAllPermissions = getBoolean("settings.console-has-all-permissions", consoleHasAllPermissions); ++ } ++ + } +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 202fa94d5dc55b549475ae0309bbcfca8f1b2c96..ec0956a98c133bcd3d4f92f696c667eab6ff98f1 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -1889,7 +1889,7 @@ public abstract class EntityHuman extends EntityLiving { + } + } + +- protected void releaseShoulderEntities() { ++ public void releaseShoulderEntities() { // Paper - protected -> public + if (this.e + 20L < this.world.getTime()) { + // CraftBukkit start + if (this.spawnEntityFromShoulder(this.getShoulderEntityLeft())) { +diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +index af986adfdb547cb61fbd52f0f89858f1a9e52cc3..80a67deaeaae3b3f0ceb9a298de5bb38b8ee707b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +@@ -86,5 +86,15 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co + public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { + this.sendRawMessage(org.bukkit.craftbukkit.util.CraftChatMessage.fromComponent(io.papermc.paper.adventure.PaperAdventure.asVanilla(message))); + } ++ ++ @Override ++ public boolean hasPermission(String name) { ++ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(name); ++ } ++ ++ @Override ++ public boolean hasPermission(org.bukkit.permissions.Permission perm) { ++ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(perm); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java +index 24662d58cb4a9bf2f3b252858b504165d91d4419..a6f3594def0abe076ff44fcfa61dd05bee729387 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java +@@ -39,4 +39,16 @@ public class CraftRemoteConsoleCommandSender extends ServerCommandSender impleme + public void setOp(boolean value) { + throw new UnsupportedOperationException("Cannot change operator status of remote controller."); + } ++ ++ // Paper start ++ @Override ++ public boolean hasPermission(String name) { ++ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(name); ++ } ++ ++ @Override ++ public boolean hasPermission(org.bukkit.permissions.Permission perm) { ++ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(perm); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0485-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch b/patches/server-unmapped/0001/0485-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch new file mode 100644 index 0000000000..5f3ad1df79 --- /dev/null +++ b/patches/server-unmapped/0001/0485-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 23 May 2020 01:31:06 -0400 +Subject: [PATCH] Fix Non Full Status Chunk NBT Memory Leak + +Any full status chunk that was requested for any status less than full +would hold onto their entire nbt tree and every variable in that function. + +This was due to use of a lambda that persists on the Chunk object +until that chunk reaches FULL status. + +With introduction of no tick, we greatly increased the number of non +full chunks so this was really starting to hurt. + +We further improve it by making a copy of the nbt tag with only the memory +it needs, so that we dont have to hold a copy to the entire compound. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +index 91bcbf7156dd90b00e2d53bb6bff4abc44ecb721..8e4924cd649c350520cba54a0e1497d5acf089ff 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +@@ -22,6 +22,7 @@ import net.minecraft.core.BlockPosition; + import net.minecraft.core.IRegistry; + import net.minecraft.core.RegistryBlocks; + import net.minecraft.core.SectionPosition; ++import net.minecraft.nbt.NBTBase; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTTagList; + import net.minecraft.nbt.NBTTagLongArray; +@@ -199,15 +200,9 @@ public class ChunkRegionLoader { + object2 = protochunkticklist1; + } + +- object = new Chunk(worldserver.getMinecraftWorld(), chunkcoordintpair, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, j, achunksection, (chunk) -> { +- loadEntities(nbttagcompound1, chunk); +- // CraftBukkit start - load chunk persistent data from nbt +- net.minecraft.nbt.NBTBase persistentBase = nbttagcompound1.get("ChunkBukkitValues"); +- if (persistentBase instanceof NBTTagCompound) { +- chunk.persistentDataContainer.putAll((NBTTagCompound) persistentBase); +- } +- // CraftBukkit end +- }); ++ object = new Chunk(worldserver.getMinecraftWorld(), chunkcoordintpair, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, j, achunksection, // Paper start - fix massive nbt memory leak due to lambda. move lambda into a container method to not leak scope. Only clone needed NBT keys. ++ createLoadEntitiesConsumer(new SafeNBTCopy(nbttagcompound1, "TileEntities", "Entities", "ChunkBukkitValues")) // Paper - move CB Chunk PDC into here ++ );// Paper end + } else { + ProtoChunk protochunk = new ProtoChunk(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, worldserver); // Paper - Anti-Xray - Add parameter + +@@ -313,6 +308,50 @@ public class ChunkRegionLoader { + return new InProgressChunkHolder(protochunk1, tasksToExecuteOnMain); // Paper - Async chunk loading + } + } ++ // Paper start ++ ++ /** ++ * This wrapper will error out if any key is accessed that wasn't copied so we can catch it easy on an update ++ */ ++ private static class SafeNBTCopy extends NBTTagCompound { ++ private final java.util.Set keys = new java.util.HashSet(); ++ public SafeNBTCopy(NBTTagCompound base, String... keys) { ++ for (String key : keys) { ++ this.keys.add(key); ++ final NBTBase nbtBase = base.get(key); ++ if (nbtBase != null) { ++ this.set(key, nbtBase); ++ } ++ } ++ } ++ ++ @Override ++ public boolean hasKey(String s) { ++ if (super.hasKey(s)) { ++ return true; ++ } else if (keys.contains(s)) { ++ return false; ++ } ++ throw new IllegalStateException("Missing Key " + s + " in SafeNBTCopy"); ++ } ++ ++ @Override ++ public boolean hasKeyOfType(String s, int i) { ++ return hasKey(s) && super.hasKeyOfType(s, i); ++ } ++ } ++ private static java.util.function.Consumer createLoadEntitiesConsumer(NBTTagCompound nbt) { ++ return (chunk) -> { ++ loadEntities(nbt, chunk); ++ // CraftBukkit start - load chunk persistent data from nbt ++ NBTBase persistentBase = nbt.get("ChunkBukkitValues"); ++ if (persistentBase instanceof NBTTagCompound) { ++ chunk.persistentDataContainer.putAll((NBTTagCompound) persistentBase); ++ } ++ // CraftBukkit end ++ }; ++ } ++ // Paper end + + // Paper start - async chunk save for unload + public static final class AsyncSaveData { diff --git a/patches/server-unmapped/0001/0486-Workaround-for-Client-Lag-Spikes-MC-162253.patch b/patches/server-unmapped/0001/0486-Workaround-for-Client-Lag-Spikes-MC-162253.patch new file mode 100644 index 0000000000..99601c7eb7 --- /dev/null +++ b/patches/server-unmapped/0001/0486-Workaround-for-Client-Lag-Spikes-MC-162253.patch @@ -0,0 +1,119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MeFisto94 +Date: Tue, 12 May 2020 23:02:43 +0200 +Subject: [PATCH] Workaround for Client Lag Spikes (MC-162253) + +When crossing certain chunk boundaries, the client needlessly +calculates light maps for chunk neighbours. In some specific map +configurations, these calculations cause a 500ms+ freeze on the Client. + +This patch basically serves as a workaround by sending light maps +to the client, so that it doesn't attempt to calculate them. +This mitigates the frametime impact to a minimum (but it's still there). + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index ede47aaaace80280756fe4463def1ea26792c9e4..d5c939281df21683efc63937a9146b0dfeb22e2c 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -85,6 +85,7 @@ import net.minecraft.world.level.World; + import net.minecraft.world.level.chunk.Chunk; + import net.minecraft.world.level.chunk.ChunkConverter; + import net.minecraft.world.level.chunk.ChunkGenerator; ++import net.minecraft.world.level.chunk.ChunkSection; + import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.IChunkAccess; + import net.minecraft.world.level.chunk.ILightAccess; +@@ -2055,9 +2056,68 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially + public final void sendChunk(EntityPlayer entityplayer, Packet[] apacket, Chunk chunk) { this.a(entityplayer, apacket, chunk); } // Paper - OBFHELPER + private void a(EntityPlayer entityplayer, Packet[] apacket, Chunk chunk) { + if (apacket[0] == null) { ++ // Paper start - add 8 for light fix workaround ++ if (apacket.length != 10) { // in case Plugins call sendChunk, resize ++ apacket = new Packet[10]; ++ } ++ // Paper end + apacket[0] = new PacketPlayOutMapChunk(chunk, 65535, chunk.world.chunkPacketBlockController.shouldModify(entityplayer, chunk, 65535)); // Paper - Anti-Xray - Bypass + apacket[1] = new PacketPlayOutLightUpdate(chunk.getPos(), this.lightEngine, true); ++ ++ // Paper start - Fix MC-162253 ++ final int lightMask = getLightMask(chunk); ++ int i = 1; ++ for (int x = -1; x <= 1; x++) { ++ for (int z = -1; z <= 1; z++) { ++ if (x == 0 && z == 0) { ++ continue; ++ } ++ ++ ++i; ++ ++ if (!chunk.isNeighbourLoaded(x, z)) { ++ continue; ++ } ++ ++ final Chunk neighbor = chunk.getRelativeNeighbourIfLoaded(x, z); ++ final int updateLightMask = lightMask & ~getCeilingLightMask(neighbor); ++ ++ if (updateLightMask == 0) { ++ continue; ++ } ++ ++ apacket[i] = new PacketPlayOutLightUpdate(new ChunkCoordIntPair(chunk.getPos().x + x, chunk.getPos().z + z), lightEngine, updateLightMask, 0, true); ++ } ++ } ++ } ++ ++ final int viewDistance = playerViewDistanceBroadcastMap.getLastViewDistance(entityplayer); ++ final long lastPosition = playerViewDistanceBroadcastMap.getLastCoordinate(entityplayer); ++ ++ int j = 1; ++ for (int x = -1; x <= 1; x++) { ++ for (int z = -1; z <= 1; z++) { ++ if (x == 0 && z == 0) { ++ continue; ++ } ++ ++ ++j; ++ ++ Packet packet = apacket[j]; ++ if (packet == null) { ++ continue; ++ } ++ ++ final int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - (chunk.getPos().x + x)); ++ final int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - (chunk.getPos().z + z)); ++ ++ if (Math.max(distX, distZ) > viewDistance) { ++ continue; ++ } ++ entityplayer.playerConnection.sendPacket(packet); ++ } + } ++ // Paper end - Fix MC-162253 + + entityplayer.a(chunk.getPos(), apacket[0], apacket[1]); + PacketDebug.a(this.world, chunk.getPos()); +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index 90e895e9eac6158a28de4a30589bf7538e5ec9cc..34a9f7b2f998f77b1279516cd09397ab6c2ac1cc 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -280,7 +280,7 @@ public class Chunk implements IChunkAccess { + + // broadcast + Object[] backingSet = inRange.getBackingSet(); +- Packet[] chunkPackets = new Packet[2]; ++ Packet[] chunkPackets = new Packet[10]; + for (int index = 0, len = backingSet.length; index < len; ++index) { + Object temp = backingSet[index]; + if (!(temp instanceof EntityPlayer)) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +index 973aa060d6964c7d470bc7aff89b879daf1df153..8fe060c3b2ad0873f96218eb7d02cdff3279224e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +@@ -107,6 +107,7 @@ public class ChunkSection { + return this.nonEmptyBlockCount == 0; + } + ++ public static boolean isEmpty(@Nullable ChunkSection chunksection) { return a(chunksection) ; } // Paper - OBFHELPER + public static boolean a(@Nullable ChunkSection chunksection) { + return chunksection == Chunk.a || chunksection.c(); + } diff --git a/patches/server-unmapped/0001/0487-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/patches/server-unmapped/0001/0487-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch new file mode 100644 index 0000000000..93f12d40b2 --- /dev/null +++ b/patches/server-unmapped/0001/0487-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch @@ -0,0 +1,1326 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 11 Apr 2020 03:56:07 -0400 +Subject: [PATCH] Implement Chunk Priority / Urgency System for Chunks + +Mark chunks that are blocking main thread for world generation as urgent + +Implements a general priority system so that chunks that are sorted in +the generator queues can prioritize certain chunks over another. + +Urgent chunks will jump to the front of the line, ensuring that a +sync chunk load on an ungenerated chunk does not lag the server for +a long period of time if the servers generator queues are filled with +lots of chunks already. + +This massively reduces the lag spikes from sync chunk gens. + +Then we further prioritize loading order so nearby chunks have higher +priority than distant chunks, reducing the pressure a high no tick +view distance holds on you. + +Chunks in front of the player have higher priority, to help with +fast traveling players keep up with their movement. + +diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java +index 8e642f450b974d81f128d26edfd40915554db638..dc641664abe8ff6b36c69c7d21a3200d160ff1b6 100644 +--- a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java ++++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java +@@ -108,7 +108,7 @@ public final class ChunkTaskManager { + } + + static void dumpChunkInfo(Set seenChunks, PlayerChunk chunkHolder, int x, int z) { +- dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1); ++ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 4); + } + + static void dumpChunkInfo(Set seenChunks, PlayerChunk chunkHolder, int x, int z, int indent, int maxDepth) { +@@ -129,6 +129,30 @@ public final class ChunkTaskManager { + PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Status - " + ((chunk == null) ? "null chunk" : chunk.getChunkStatus().toString())); + PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Ticket Status - " + PlayerChunk.getChunkStatus(chunkHolder.getTicketLevel())); + PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Status - " + ((holderStatus == null) ? "null" : holderStatus.toString())); ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Priority - " + chunkHolder.getCurrentPriority()); ++ ++ if (!chunkHolder.neighbors.isEmpty()) { ++ if (indent >= maxDepth) { ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: (Can't show, too deeply nested)"); ++ return; ++ } ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: "); ++ for (PlayerChunk neighbor : chunkHolder.neighbors.keySet()) { ++ ChunkStatus status = neighbor.getChunkHolderStatus(); ++ if (status != null && status.isAtLeastStatus(PlayerChunk.getChunkStatus(neighbor.getTicketLevel()))) { ++ continue; ++ } ++ int nx = neighbor.location.x; ++ int nz = neighbor.location.z; ++ if (seenChunks.contains(neighbor)) { ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + " (CIRCULAR)"); ++ continue; ++ } ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + ":"); ++ dumpChunkInfo(seenChunks, neighbor, nx, nz, indent + 1, maxDepth); ++ } ++ } ++ + } + } + +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 17de074111a174f3a39a4477afc3ad62e04a73b5..1d72af9cace7aa8f1d20c7c1c5be621f533e2dad 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -673,6 +673,7 @@ public final class MCUtil { + chunkData.addProperty("x", playerChunk.location.x); + chunkData.addProperty("z", playerChunk.location.z); + chunkData.addProperty("ticket-level", playerChunk.getTicketLevel()); ++ chunkData.addProperty("priority", playerChunk.getCurrentPriority()); + chunkData.addProperty("state", PlayerChunk.getChunkState(playerChunk.getTicketLevel()).toString()); + chunkData.addProperty("queued-for-unload", chunkMap.unloadQueue.contains(playerChunk.location.pair())); + chunkData.addProperty("status", status == null ? "unloaded" : status.toString()); +diff --git a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +index 2bbdcedf4856080ea9232effdf3bdae9c26c425b..a3c44fdfca8290313b9b1117b984533183b583ad 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +@@ -21,7 +21,10 @@ import java.util.Set; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.Executor; + import javax.annotation.Nullable; ++import net.minecraft.core.BlockPosition; + import net.minecraft.core.SectionPosition; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.util.ArraySetSorted; + import net.minecraft.util.thread.Mailbox; + import net.minecraft.world.level.ChunkCoordIntPair; +@@ -29,6 +32,7 @@ import net.minecraft.world.level.chunk.Chunk; + import net.minecraft.world.level.chunk.ChunkStatus; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.spigotmc.AsyncCatcher; // Paper + + public abstract class ChunkMapDistance { + +@@ -52,7 +56,7 @@ public abstract class ChunkMapDistance { + private final ChunkTaskQueueSorter i; + private final Mailbox> j; + private final Mailbox k; +- private final LongSet l = new LongOpenHashSet(); ++ private final LongSet l = new LongOpenHashSet(); public final LongSet getOnPlayerTicketAddQueue() { return l; } // Paper - OBFHELPER + private final Executor m; + private long currentTick; + +@@ -90,6 +94,7 @@ public abstract class ChunkMapDistance { + } + + private static int getLowestTicketLevel(ArraySetSorted> arraysetsorted) { ++ AsyncCatcher.catchOp("ChunkMapDistance::getLowestTicketLevel"); // Paper + return !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.b()).b() : PlayerChunkMap.GOLDEN_TICKET + 1; + } + +@@ -103,6 +108,7 @@ public abstract class ChunkMapDistance { + + public boolean a(PlayerChunkMap playerchunkmap) { + //this.f.a(); // Paper - no longer used ++ AsyncCatcher.catchOp("DistanceManagerTick"); // Paper + this.g.a(); + int i = Integer.MAX_VALUE - this.ticketLevelTracker.a(Integer.MAX_VALUE); + boolean flag = i != 0; +@@ -113,11 +119,13 @@ public abstract class ChunkMapDistance { + + // Paper start + if (!this.pendingChunkUpdates.isEmpty()) { ++ this.pollingPendingChunkUpdates = true; try { + while(!this.pendingChunkUpdates.isEmpty()) { + PlayerChunk remove = this.pendingChunkUpdates.remove(); + remove.isUpdateQueued = false; + remove.a(playerchunkmap); + } ++ } finally { this.pollingPendingChunkUpdates = false; } + // Paper end + return true; + } else { +@@ -153,8 +161,10 @@ public abstract class ChunkMapDistance { + return flag; + } + } ++ boolean pollingPendingChunkUpdates = false; // Paper + + private boolean addTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean ++ AsyncCatcher.catchOp("ChunkMapDistance::addTicket"); // Paper + ArraySetSorted> arraysetsorted = this.e(i); + int j = getLowestTicketLevel(arraysetsorted); + Ticket ticket1 = (Ticket) arraysetsorted.a(ticket); // CraftBukkit - decompile error +@@ -168,7 +178,9 @@ public abstract class ChunkMapDistance { + } + + private boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean ++ AsyncCatcher.catchOp("ChunkMapDistance::removeTicket"); // Paper + ArraySetSorted> arraysetsorted = this.e(i); ++ int oldLevel = getLowestTicketLevel(arraysetsorted); // Paper + + boolean removed = false; // CraftBukkit + if (arraysetsorted.remove(ticket)) { +@@ -179,7 +191,8 @@ public abstract class ChunkMapDistance { + this.tickets.remove(i); + } + +- this.ticketLevelTracker.update(i, getLowestTicketLevel(arraysetsorted), false); ++ int newLevel = getLowestTicketLevel(arraysetsorted); // Paper ++ if (newLevel > oldLevel) this.ticketLevelTracker.update(i, newLevel, false); // Paper + return removed; // CraftBukkit + } + +@@ -188,6 +201,135 @@ public abstract class ChunkMapDistance { + this.addTicketAtLevel(tickettype, chunkcoordintpair, i, t0); + } + ++ // Paper start ++ public static final int PRIORITY_TICKET_LEVEL = PlayerChunkMap.GOLDEN_TICKET; ++ public static final int URGENT_PRIORITY = 29; ++ public boolean delayDistanceManagerTick = false; ++ public boolean markUrgent(ChunkCoordIntPair coords) { ++ return addPriorityTicket(coords, TicketType.URGENT, URGENT_PRIORITY); ++ } ++ public boolean markHighPriority(ChunkCoordIntPair coords, int priority) { ++ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority)); ++ return addPriorityTicket(coords, TicketType.PRIORITY, priority); ++ } ++ ++ public void markAreaHighPriority(ChunkCoordIntPair center, int priority, int radius) { ++ delayDistanceManagerTick = true; ++ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority)); ++ int finalPriority = priority; ++ MCUtil.getSpiralOutChunks(center.asPosition(), radius).forEach(coords -> { ++ addPriorityTicket(coords, TicketType.PRIORITY, finalPriority); ++ }); ++ delayDistanceManagerTick = false; ++ chunkMap.world.getChunkProvider().tickDistanceManager(); ++ } ++ ++ public void clearAreaPriorityTickets(ChunkCoordIntPair center, int radius) { ++ delayDistanceManagerTick = true; ++ MCUtil.getSpiralOutChunks(center.asPosition(), radius).forEach(coords -> { ++ this.removeTicket(coords.pair(), new Ticket(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords)); ++ }); ++ delayDistanceManagerTick = false; ++ chunkMap.world.getChunkProvider().tickDistanceManager(); ++ } ++ ++ private boolean hasPlayerTicket(ChunkCoordIntPair coords, int level) { ++ ArraySetSorted> tickets = this.tickets.get(coords.pair()); ++ if (tickets == null || tickets.isEmpty()) { ++ return false; ++ } ++ for (Ticket ticket : tickets) { ++ if (ticket.getTicketType() == TicketType.PLAYER && ticket.getTicketLevel() == level) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ private boolean addPriorityTicket(ChunkCoordIntPair coords, TicketType ticketType, int priority) { ++ AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket"); ++ long pair = coords.pair(); ++ PlayerChunk chunk = chunkMap.getUpdatingChunk(pair); ++ boolean needsTicket = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(pair) != null && !hasPlayerTicket(coords, 33); ++ ++ if (needsTicket) { ++ Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, coords); ++ getOnPlayerTicketAddQueue().add(pair); ++ addTicket(pair, ticket); ++ } ++ if ((chunk != null && chunk.isFullChunkReady())) { ++ if (needsTicket) { ++ chunkMap.world.getChunkProvider().tickDistanceManager(); ++ } ++ return needsTicket; ++ } ++ ++ boolean success; ++ if (!(success = updatePriorityTicket(coords, ticketType, priority))) { ++ Ticket ticket = new Ticket(ticketType, PRIORITY_TICKET_LEVEL, coords); ++ ticket.priority = priority; ++ success = this.addTicket(pair, ticket); ++ } else { ++ if (chunk == null) { ++ chunk = chunkMap.getUpdatingChunk(pair); ++ } ++ chunkMap.queueHolderUpdate(chunk); ++ } ++ ++ //chunkMap.world.getWorld().spawnParticle(priority <= 15 ? org.bukkit.Particle.EXPLOSION_HUGE : org.bukkit.Particle.EXPLOSION_NORMAL, chunkMap.world.getWorld().getPlayers(), null, coords.x << 4, 70, coords.z << 4, 2, 0, 0, 0, 1, null, true); ++ ++ chunkMap.world.getChunkProvider().tickDistanceManager(); ++ ++ return success; ++ } ++ ++ private boolean updatePriorityTicket(ChunkCoordIntPair coords, TicketType type, int priority) { ++ ArraySetSorted> tickets = this.tickets.get(coords.pair()); ++ if (tickets == null) { ++ return false; ++ } ++ for (Ticket ticket : tickets) { ++ if (ticket.getTicketType() == type) { ++ // We only support increasing, not decreasing, too complicated ++ ticket.setCurrentTick(this.currentTick); ++ ticket.priority = Math.max(ticket.priority, priority); ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ public int getChunkPriority(ChunkCoordIntPair coords) { ++ AsyncCatcher.catchOp("ChunkMapDistance::getChunkPriority"); ++ ArraySetSorted> tickets = this.tickets.get(coords.pair()); ++ if (tickets == null) { ++ return 0; ++ } ++ for (Ticket ticket : tickets) { ++ if (ticket.getTicketType() == TicketType.URGENT) { ++ return URGENT_PRIORITY; ++ } ++ } ++ for (Ticket ticket : tickets) { ++ if (ticket.getTicketType() == TicketType.PRIORITY && ticket.priority > 0) { ++ return ticket.priority; ++ } ++ } ++ return 0; ++ } ++ ++ public void clearPriorityTickets(ChunkCoordIntPair coords) { ++ AsyncCatcher.catchOp("ChunkMapDistance::clearPriority"); ++ this.removeTicket(coords.pair(), new Ticket(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords)); ++ } ++ ++ public void clearUrgent(ChunkCoordIntPair coords) { ++ AsyncCatcher.catchOp("ChunkMapDistance::clearUrgent"); ++ this.removeTicket(coords.pair(), new Ticket(TicketType.URGENT, PRIORITY_TICKET_LEVEL, coords)); ++ } ++ // Paper end + public boolean addTicketAtLevel(TicketType ticketType, ChunkCoordIntPair chunkcoordintpair, int level, T identifier) { + return this.addTicket(chunkcoordintpair.pair(), new Ticket<>(ticketType, level, identifier)); + // CraftBukkit end +@@ -358,7 +500,7 @@ public abstract class ChunkMapDistance { + + class c extends ChunkMapDistance.b { + +- private int e = 0; ++ private int e = 0; private int getViewDistance() { return e; } private void setViewDistance(int value) { this.e = value; } // Paper - OBFHELPER + private final Long2IntMap f = Long2IntMaps.synchronize(new Long2IntOpenHashMap()); + private final LongSet g = new LongOpenHashSet(); + +@@ -374,41 +516,68 @@ public abstract class ChunkMapDistance { + + public void a(int i) { + ObjectIterator objectiterator = this.a.long2ByteEntrySet().iterator(); ++ // Paper start - set the view distance before scheduling chunk loads/unloads ++ int lastViewDistance = getViewDistance(); ++ setViewDistance(i); ++ // Paper end + + while (objectiterator.hasNext()) { + Long2ByteMap.Entry it_unimi_dsi_fastutil_longs_long2bytemap_entry = (Long2ByteMap.Entry) objectiterator.next(); // Paper - decompile fix + byte b0 = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getByteValue(); + long j = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getLongKey(); + +- this.a(j, b0, this.c(b0), b0 <= i - 2); ++ this.a(j, b0, b0 <= lastViewDistance - 2, this.c(b0)); // Paper + } + +- this.e = i; ++ //this.e = i; // Paper - view distance is now set further up + } + + private void a(long i, int j, boolean flag, boolean flag1) { + if (flag != flag1) { +- Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkCoordIntPair(i)); // Paper - no-tick view distance ++ ChunkCoordIntPair coords = new ChunkCoordIntPair(i); // Paper ++ Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, coords); // Paper - no-tick view distance + + if (flag1) { +- ChunkMapDistance.this.j.a(ChunkTaskQueueSorter.a(() -> { ++ scheduleChunkLoad(i, MinecraftServer.currentTick, j, (priority) -> { // Paper - smarter ticket delay based on frustum and distance ++ // Paper start - recheck its still valid if not cancel ++ if (!isChunkInRange(i)) { ++ ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { ++ ChunkMapDistance.this.m.execute(() -> { ++ ChunkMapDistance.this.removeTicket(i, ticket); ++ ChunkMapDistance.this.clearPriorityTickets(coords); ++ }); ++ }, i, false)); ++ return; ++ } ++ // abort early if we got a ticket already ++ if (hasPlayerTicket(coords, 33)) return; ++ // skip player ticket throttle for near chunks ++ if (priority <= 3) { ++ ChunkMapDistance.this.addTicket(i, ticket); ++ ChunkMapDistance.this.l.add(i); ++ return; ++ } ++ // Paper end ++ ChunkMapDistance.this.j.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error + ChunkMapDistance.this.m.execute(() -> { +- if (this.c(this.c(i))) { ++ if (isChunkInRange(i)) { if (!hasPlayerTicket(coords, 33)) { // Paper - high priority might of already added it + ChunkMapDistance.this.addTicket(i, ticket); + ChunkMapDistance.this.l.add(i); +- } else { +- ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { ++ }} else { // Paper ++ ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error + }, i, false)); + } + + }); + }, i, () -> { +- return j; ++ return Math.min(PlayerChunkMap.GOLDEN_TICKET, priority); // Paper + })); ++ }); // Paper + } else { + ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { + ChunkMapDistance.this.m.execute(() -> { + ChunkMapDistance.this.removeTicket(i, ticket); ++ ChunkMapDistance.this.clearPriorityTickets(coords); // Paper + }); + }, i, true)); + } +@@ -416,6 +585,101 @@ public abstract class ChunkMapDistance { + + } + ++ // Paper start - smart scheduling of player tickets ++ private boolean isChunkInRange(long i) { ++ return this.isLoadedChunkLevel(this.getChunkLevel(i)); ++ } ++ public void scheduleChunkLoad(long i, long startTick, int initialDistance, java.util.function.Consumer task) { ++ long elapsed = MinecraftServer.currentTick - startTick; ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(i); ++ PlayerChunk updatingChunk = chunkMap.getUpdatingChunk(i); ++ if ((updatingChunk != null && updatingChunk.isFullChunkReady()) || !isChunkInRange(i) || getChunkPriority(chunkPos) > 0) { // Copied from above ++ // no longer needed ++ task.accept(1); ++ return; ++ } ++ ++ int desireDelay = 0; ++ double minDist = Double.MAX_VALUE; ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(i); ++ if (elapsed == 0 && initialDistance <= 4) { ++ // Aim for no delay on initial 6 chunk radius tickets save on performance of the below code to only > 6 ++ minDist = initialDistance; ++ } else if (players != null) { ++ Object[] backingSet = players.getBackingSet(); ++ ++ BlockPosition blockPos = chunkPos.asPosition(); ++ ++ boolean isFront = false; ++ BlockPosition.MutableBlockPosition pos = new BlockPosition.MutableBlockPosition(); ++ for (int index = 0, len = backingSet.length; index < len; ++index) { ++ if (!(backingSet[index] instanceof EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer) backingSet[index]; ++ ++ ChunkCoordIntPair pointInFront = player.getChunkInFront(5); ++ pos.setValues(pointInFront.x << 4, 0, pointInFront.z << 4); ++ double frontDist = MCUtil.distanceSq(pos, blockPos); ++ ++ pos.setValues(player.locX(), 0, player.locZ()); ++ double center = MCUtil.distanceSq(pos, blockPos); ++ ++ double dist = Math.min(frontDist, center); ++ if (!isFront) { ++ ChunkCoordIntPair pointInBack = player.getChunkInFront(-7); ++ pos.setValues(pointInBack.x << 4, 0, pointInBack.z << 4); ++ double backDist = MCUtil.distanceSq(pos, blockPos); ++ if (frontDist < backDist) { ++ isFront = true; ++ } ++ } ++ if (dist < minDist) { ++ minDist = dist; ++ } ++ } ++ if (minDist == Double.MAX_VALUE) { ++ minDist = 15; ++ } else { ++ minDist = Math.sqrt(minDist) / 16; ++ } ++ if (minDist > 4) { ++ int desiredTimeDelayMax = isFront ? ++ (minDist < 10 ? 7 : 15) : // Front ++ (minDist < 10 ? 15 : 45); // Back ++ desireDelay += (desiredTimeDelayMax * 20) * (minDist / 32); ++ } ++ } else { ++ minDist = initialDistance; ++ desireDelay = 1; ++ } ++ long delay = desireDelay - elapsed; ++ if (delay <= 0 && minDist > 4 && minDist < Double.MAX_VALUE) { ++ boolean hasAnyNeighbor = false; ++ for (int x = -1; x <= 1; x++) { ++ for (int z = -1; z <= 1; z++) { ++ if (x == 0 && z == 0) continue; ++ long pair = ChunkCoordIntPair.pair(chunkPos.x + x, chunkPos.z + z); ++ PlayerChunk neighbor = chunkMap.getUpdatingChunk(pair); ++ ChunkStatus current = neighbor != null ? neighbor.getChunkHolderStatus() : null; ++ if (current != null && current.isAtLeastStatus(ChunkStatus.LIGHT)) { ++ hasAnyNeighbor = true; ++ } ++ } ++ } ++ if (!hasAnyNeighbor) { ++ delay += 20; ++ } ++ } ++ if (delay <= 0) { ++ task.accept((int) minDist); ++ } else { ++ int taskDelay = (int) Math.min(delay, minDist >= 10 ? 40 : (minDist < 6 ? 5 : 20)); ++ MCUtil.scheduleTask(taskDelay, () -> scheduleChunkLoad(i, startTick, initialDistance, task), "Player Ticket Delayer"); ++ } ++ } ++ // Paper end ++ + @Override + public void a() { + super.a(); +@@ -447,6 +711,7 @@ public abstract class ChunkMapDistance { + + } + ++ private boolean isLoadedChunkLevel(int i) { return c(i); } // Paper - OBFHELPER + private boolean c(int i) { + return i <= this.e - 2; + } +@@ -463,6 +728,7 @@ public abstract class ChunkMapDistance { + this.a.defaultReturnValue((byte) (i + 2)); + } + ++ protected final int getChunkLevel(long i) { return c(i); } // Paper - OBFHELPER + @Override + protected int c(long i) { + return this.a.get(i); +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index bb6b447178c410f53b303b8a633297d2f0c09a0d..8acb8ef15cfe9c54f56c1c8da02940ab32ceb8c5 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -468,6 +468,26 @@ public class ChunkProviderServer extends IChunkProvider { + public void removeTicketAtLevel(TicketType ticketType, ChunkCoordIntPair chunkPos, int ticketLevel, T identifier) { + this.chunkMapDistance.removeTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier); + } ++ ++ public boolean markUrgent(ChunkCoordIntPair coords) { ++ return this.chunkMapDistance.markUrgent(coords); ++ } ++ ++ public boolean markHighPriority(ChunkCoordIntPair coords, int priority) { ++ return this.chunkMapDistance.markHighPriority(coords, priority); ++ } ++ ++ public void markAreaHighPriority(ChunkCoordIntPair center, int priority, int radius) { ++ this.chunkMapDistance.markAreaHighPriority(center, priority, radius); ++ } ++ ++ public void clearAreaPriorityTickets(ChunkCoordIntPair center, int radius) { ++ this.chunkMapDistance.clearAreaPriorityTickets(center, radius); ++ } ++ ++ public void clearPriorityTickets(ChunkCoordIntPair coords) { ++ this.chunkMapDistance.clearPriorityTickets(coords); ++ } + // Paper end + + @Nullable +@@ -506,6 +526,8 @@ public class ChunkProviderServer extends IChunkProvider { + + if (!completablefuture.isDone()) { // Paper + // Paper start - async chunk io/loading ++ ChunkCoordIntPair pair = new ChunkCoordIntPair(x, z); ++ this.chunkMapDistance.markUrgent(pair); + this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z); + // Paper end +@@ -514,6 +536,8 @@ public class ChunkProviderServer extends IChunkProvider { + this.serverThreadQueue.awaitTasks(completablefuture::isDone); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug + this.world.timings.syncChunkLoad.stopTiming(); // Paper ++ this.chunkMapDistance.clearPriorityTickets(pair); // Paper ++ this.chunkMapDistance.clearUrgent(pair); // Paper + } // Paper + ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { + return ichunkaccess1; +@@ -566,10 +590,12 @@ public class ChunkProviderServer extends IChunkProvider { + if (flag && !currentlyUnloading) { + // CraftBukkit end + this.chunkMapDistance.a(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); ++ if (isUrgent) this.chunkMapDistance.markUrgent(chunkcoordintpair); // Paper + if (this.a(playerchunk, l)) { + GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); + + gameprofilerfiller.enter("chunkLoad"); ++ chunkMapDistance.delayDistanceManagerTick = false; // Paper - ensure this is never false + this.tickDistanceManager(); + playerchunk = this.getChunk(k); + gameprofilerfiller.exit(); +@@ -578,8 +604,13 @@ public class ChunkProviderServer extends IChunkProvider { + } + } + } +- +- return this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap); ++ // Paper start ++ CompletableFuture> future = this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap); ++ if (isUrgent) { ++ future.thenAccept(either -> this.chunkMapDistance.clearUrgent(chunkcoordintpair)); ++ } ++ return future; ++ // Paper end + } + + private boolean a(@Nullable PlayerChunk playerchunk, int i) { +@@ -631,6 +662,7 @@ public class ChunkProviderServer extends IChunkProvider { + } + + public boolean tickDistanceManager() { // Paper - private -> public ++ if (chunkMapDistance.delayDistanceManagerTick) return false; // Paper + boolean flag = this.chunkMapDistance.a(this.playerChunkMap); + boolean flag1 = this.playerChunkMap.b(); + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 5b3ccc8e623712cef2afeb16dac001ee4d3423d1..21d1b4e0fe93b28cdadac043d081306d32032f81 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -73,6 +73,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutWorldEvent; + import net.minecraft.resources.MinecraftKey; + import net.minecraft.resources.ResourceKey; + import net.minecraft.server.AdvancementDataPlayer; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.network.ITextFilter; + import net.minecraft.server.network.PlayerConnection; +@@ -187,6 +188,12 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + private int lastArmorScored = Integer.MIN_VALUE; + private int lastExpLevelScored = Integer.MIN_VALUE; + private int lastExpTotalScored = Integer.MIN_VALUE; ++ public long lastHighPriorityChecked; // Paper ++ public void forceCheckHighPriority() { ++ lastHighPriorityChecked = -1; ++ getWorldServer().getChunkProvider().playerChunkMap.checkHighPriorityChunks(this); ++ } ++ public boolean isRealPlayer; // Paper + private float lastHealthSent = -1.0E8F; + private int lastFoodSent = -99999999; + private boolean lastSentSaturationZero = true; +@@ -274,6 +281,21 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.maxHealthCache = this.getMaxHealth(); + this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper + } ++ // Paper start ++ public BlockPosition getPointInFront(double inFront) { ++ double rads = Math.toRadians(MCUtil.normalizeYaw(this.yaw+90)); // MC rotates yaw 90 for some odd reason ++ final double x = locX() + inFront * Math.cos(rads); ++ final double z = locZ() + inFront * Math.sin(rads); ++ return new BlockPosition(x, locY(), z); ++ } ++ ++ public ChunkCoordIntPair getChunkInFront(double inFront) { ++ double rads = Math.toRadians(MCUtil.normalizeYaw(this.yaw+90)); // MC rotates yaw 90 for some odd reason ++ final double x = locX() + (inFront * 16) * Math.cos(rads); ++ final double z = locZ() + (inFront * 16) * Math.sin(rads); ++ return new ChunkCoordIntPair(MathHelper.floor(x) >> 4, MathHelper.floor(z) >> 4); ++ } ++ // Paper end + + // Yes, this doesn't match Vanilla, but it's the best we can do for now. + // If this is an issue, PRs are welcome +@@ -621,6 +643,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + if (valid && !this.isSpectator() || this.world.isLoaded(this.getChunkCoordinates())) { // Paper - don't tick dead players that are not in the world currently (pending respawn) + super.tick(); + } ++ if (valid && isAlive() && playerConnection != null) ((WorldServer)world).getChunkProvider().playerChunkMap.checkHighPriorityChunks(this); // Paper + + for (int i = 0; i < this.inventory.getSize(); ++i) { + ItemStack itemstack = this.inventory.getItem(i); +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index e53054fc46e528f9c713eb4c03add61316e19396..fc79a73c884ceb7e0ce56443c36b135c4e525193 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -1,6 +1,7 @@ + package net.minecraft.server.level; + + import com.mojang.datafixers.util.Either; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper + import it.unimi.dsi.fastutil.shorts.ShortArraySet; + import it.unimi.dsi.fastutil.shorts.ShortSet; + import java.util.List; +@@ -19,6 +20,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutBlockChange; + import net.minecraft.network.protocol.game.PacketPlayOutLightUpdate; + import net.minecraft.network.protocol.game.PacketPlayOutMultiBlockChange; + import net.minecraft.network.protocol.game.PacketPlayOutTileEntityData; ++import net.minecraft.server.MCUtil; + import net.minecraft.util.MathHelper; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.EnumSkyBlock; +@@ -53,8 +55,8 @@ public class PlayerChunk { + private CompletableFuture chunkSave; + public int oldTicketLevel; + private int ticketLevel; +- private int n; +- final ChunkCoordIntPair location; // Paper - private -> package ++ volatile int n; public final int getCurrentPriority() { return n; } // Paper - OBFHELPER - make volatile since this is concurrently accessed ++ public final ChunkCoordIntPair location; // Paper - private -> public + private boolean p; + private final ShortSet[] dirtyBlocks; + private int r; +@@ -66,6 +68,7 @@ public class PlayerChunk { + private boolean x; + + private final PlayerChunkMap chunkMap; // Paper ++ public WorldServer getWorld() { return chunkMap.world; } // Paper + + long lastAutoSaveTime; // Paper - incremental autosave + long inactiveTimeStart; // Paper - incremental autosave +@@ -93,6 +96,120 @@ public class PlayerChunk { + return null; + } + // Paper end - no-tick view distance ++ // Paper start - Chunk gen/load priority system ++ volatile int neighborPriority = -1; ++ volatile int priorityBoost = 0; ++ public final java.util.concurrent.ConcurrentHashMap neighbors = new java.util.concurrent.ConcurrentHashMap<>(); ++ public final Long2ObjectOpenHashMap neighborPriorities = new Long2ObjectOpenHashMap<>(); ++ ++ private int getDemandedPriority() { ++ int priority = neighborPriority; // if we have a neighbor priority, use it ++ int myPriority = getMyPriority(); ++ ++ if (priority == -1 || (ticketLevel <= 33 && priority > myPriority)) { ++ priority = myPriority; ++ } ++ ++ return Math.max(1, Math.min(Math.max(ticketLevel, PlayerChunkMap.GOLDEN_TICKET), priority)); ++ } ++ ++ private int getMyPriority() { ++ if (priorityBoost == ChunkMapDistance.URGENT_PRIORITY) { ++ return 2; // Urgent - ticket level isn't always 31 so 33-30 = 3, but allow 1 more tasks to go below this for dependents ++ } ++ return ticketLevel - priorityBoost; ++ } ++ ++ private int getNeighborsPriority() { ++ return (neighborPriorities.isEmpty() ? getMyPriority() : getDemandedPriority()) + 1; ++ } ++ ++ public void onNeighborRequest(PlayerChunk neighbor, ChunkStatus status) { ++ neighbor.setNeighborPriority(this, getNeighborsPriority()); ++ this.neighbors.compute(neighbor, (playerChunk, currentWantedStatus) -> { ++ if (currentWantedStatus == null || !currentWantedStatus.isAtLeastStatus(status)) { ++ //System.out.println(this + " request " + neighbor + " at " + status + " currently " + currentWantedStatus); ++ return status; ++ } else { ++ //System.out.println(this + " requested " + neighbor + " at " + status + " but thats lower than other wanted status " + currentWantedStatus); ++ return currentWantedStatus; ++ } ++ }); ++ ++ } ++ ++ public void onNeighborDone(PlayerChunk neighbor, ChunkStatus chunkstatus, IChunkAccess chunk) { ++ this.neighbors.compute(neighbor, (playerChunk, wantedStatus) -> { ++ if (wantedStatus != null && chunkstatus.isAtLeastStatus(wantedStatus)) { ++ //System.out.println(this + " neighbor done at " + neighbor + " for status " + chunkstatus + " wanted " + wantedStatus); ++ neighbor.removeNeighborPriority(this); ++ return null; ++ } else { ++ //System.out.println(this + " neighbor finished our previous request at " + neighbor + " for status " + chunkstatus + " but we now want instead " + wantedStatus); ++ return wantedStatus; ++ } ++ }); ++ } ++ ++ private void removeNeighborPriority(PlayerChunk requester) { ++ synchronized (neighborPriorities) { ++ neighborPriorities.remove(requester.location.pair()); ++ recalcNeighborPriority(); ++ } ++ checkPriority(); ++ } ++ ++ ++ private void setNeighborPriority(PlayerChunk requester, int priority) { ++ synchronized (neighborPriorities) { ++ neighborPriorities.put(requester.location.pair(), Integer.valueOf(priority)); ++ recalcNeighborPriority(); ++ } ++ checkPriority(); ++ } ++ ++ private void recalcNeighborPriority() { ++ neighborPriority = -1; ++ if (!neighborPriorities.isEmpty()) { ++ synchronized (neighborPriorities) { ++ for (Integer neighbor : neighborPriorities.values()) { ++ if (neighbor < neighborPriority || neighborPriority == -1) { ++ neighborPriority = neighbor; ++ } ++ } ++ } ++ } ++ } ++ private void checkPriority() { ++ if (getCurrentPriority() != getDemandedPriority()) this.chunkMap.queueHolderUpdate(this); ++ } ++ ++ public final double getDistance(EntityPlayer player) { ++ return getDistance(player.locX(), player.locZ()); ++ } ++ public final double getDistance(double blockX, double blockZ) { ++ int cx = MCUtil.fastFloor(blockX) >> 4; ++ int cz = MCUtil.fastFloor(blockZ) >> 4; ++ final double x = location.x - cx; ++ final double z = location.z - cz; ++ return (x * x) + (z * z); ++ } ++ ++ public final double getDistanceFrom(BlockPosition pos) { ++ return getDistance(pos.getX(), pos.getZ()); ++ } ++ ++ @Override ++ public String toString() { ++ return "PlayerChunk{" + ++ "location=" + location + ++ ", ticketLevel=" + ticketLevel + "/" + getChunkStatus(this.ticketLevel) + ++ ", chunkHolderStatus=" + getChunkHolderStatus() + ++ ", neighborPriority=" + getNeighborsPriority() + ++ ", priority=(" + ticketLevel + " - " + priorityBoost +" vs N " + neighborPriority + ") = " + getDemandedPriority() + " A " + getCurrentPriority() + ++ '}'; ++ } ++ // Paper end + + public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { + this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); +@@ -195,6 +312,18 @@ public class PlayerChunk { + } + return null; + } ++ public static ChunkStatus getNextStatus(ChunkStatus status) { ++ if (status == ChunkStatus.FULL) { ++ return status; ++ } ++ return CHUNK_STATUSES.get(status.getStatusIndex() + 1); ++ } ++ public CompletableFuture> getStatusFutureUncheckedMain(ChunkStatus chunkstatus) { ++ return ensureMain(getStatusFutureUnchecked(chunkstatus)); ++ } ++ public CompletableFuture ensureMain(CompletableFuture future) { ++ return future.thenApplyAsync(r -> r, chunkMap.mainInvokingExecutor); ++ } + // Paper end + + public CompletableFuture> getStatusFutureUnchecked(ChunkStatus chunkstatus) { +@@ -441,6 +570,7 @@ public class PlayerChunk { + return this.n; + } + ++ private void setPriority(int i) { d(i); } // Paper - OBFHELPER + private void d(int i) { + this.n = i; + } +@@ -459,7 +589,7 @@ public class PlayerChunk { + // CraftBukkit start + // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. + if (playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && !playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) { +- this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { ++ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main + Chunk chunk = (Chunk)either.left().orElse(null); + if (chunk != null) { + playerchunkmap.callbackExecutor.execute(() -> { +@@ -524,12 +654,13 @@ public class PlayerChunk { + if (!flag2 && flag3) { + // Paper start - cache ticking ready status + int expectCreateCount = ++this.fullChunkCreateCount; +- this.fullChunkFuture = playerchunkmap.b(this); this.fullChunkFuture.thenAccept((either) -> { ++ this.fullChunkFuture = playerchunkmap.b(this); ensureMain(this.fullChunkFuture).thenAccept((either) -> { // Paper - ensure main + if (either.left().isPresent() && PlayerChunk.this.fullChunkCreateCount == expectCreateCount) { + // note: Here is a very good place to add callbacks to logic waiting on this. + Chunk fullChunk = either.left().get(); + PlayerChunk.this.isFullChunkReady = true; + fullChunk.playerChunk = PlayerChunk.this; ++ this.chunkMap.chunkDistanceManager.clearPriorityTickets(location); + + + } +@@ -554,7 +685,7 @@ public class PlayerChunk { + + if (!flag4 && flag5) { + // Paper start - cache ticking ready status +- this.tickingFuture = playerchunkmap.a(this); this.tickingFuture.thenAccept((either) -> { ++ this.tickingFuture = playerchunkmap.a(this); ensureMain(this.tickingFuture).thenAccept((either) -> { // Paper - ensure main + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + Chunk tickingChunk = either.left().get(); +@@ -585,7 +716,7 @@ public class PlayerChunk { + } + + // Paper start - cache ticking ready status +- this.entityTickingFuture = playerchunkmap.b(this.location); this.entityTickingFuture.thenAccept((either) -> { ++ this.entityTickingFuture = playerchunkmap.b(this.location); ensureMain(this.entityTickingFuture).thenAccept((either) -> { // Paper ensureMain + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + Chunk entityTickingChunk = either.left().get(); +@@ -605,12 +736,29 @@ public class PlayerChunk { + this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; + } + +- this.u.a(this.location, this::k, this.ticketLevel, this::d); ++ // Paper start - raise IO/load priority if priority changes, use our preferred priority ++ priorityBoost = chunkMap.chunkDistanceManager.getChunkPriority(location); ++ int priority = getDemandedPriority(); ++ if (getCurrentPriority() > priority) { ++ int ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY; ++ if (priority <= 10) { ++ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; ++ } else if (priority <= 20) { ++ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; ++ } ++ chunkMap.world.asyncChunkTaskManager.raisePriority(location.x, location.z, ioPriority); ++ } ++ if (getCurrentPriority() != priority) { ++ this.u.a(this.location, this::getCurrentPriority, priority, this::setPriority); // use preferred priority ++ int neighborsPriority = getNeighborsPriority(); ++ this.neighbors.forEach((neighbor, neighborDesired) -> neighbor.setNeighborPriority(this, neighborsPriority)); ++ } ++ // Paper end + 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 (!playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) { +- this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { ++ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main + Chunk chunk = (Chunk)either.left().orElse(null); + if (chunk != null) { + playerchunkmap.callbackExecutor.execute(() -> { +@@ -692,6 +840,7 @@ public class PlayerChunk { + + public interface c { + ++ default void changePriority(ChunkCoordIntPair chunkcoordintpair, IntSupplier intsupplier, int i, IntConsumer intconsumer) { a(chunkcoordintpair, intsupplier, i, intconsumer); } // Paper - OBFHELPER + void a(ChunkCoordIntPair chunkcoordintpair, IntSupplier intsupplier, int i, IntConsumer intconsumer); + } + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index d5c939281df21683efc63937a9146b0dfeb22e2c..01cce21eeed25b2bb36a0f32b9708afb83690f90 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -14,6 +14,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; + import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + import it.unimi.dsi.fastutil.longs.Long2ByteMap; + import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; // Paper + import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; + import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; + import it.unimi.dsi.fastutil.longs.LongIterator; +@@ -51,6 +52,7 @@ import net.minecraft.CrashReport; + import net.minecraft.CrashReportSystemDetails; + import net.minecraft.ReportedException; + import net.minecraft.SystemUtils; ++import net.minecraft.core.BlockPosition; + import net.minecraft.core.SectionPosition; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.protocol.Packet; +@@ -105,6 +107,7 @@ import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + + import org.bukkit.entity.Player; // CraftBukkit ++import org.spigotmc.AsyncCatcher; + + public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + +@@ -142,6 +145,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + public final WorldServer world; + private final LightEngineThreaded lightEngine; + private final IAsyncTaskHandler executor; ++ final java.util.concurrent.Executor mainInvokingExecutor; // Paper + public final ChunkGenerator chunkGenerator; + private final Supplier l; public final Supplier getWorldPersistentDataSupplier() { return this.l; } // Paper - OBFHELPER + private final VillagePlace m; +@@ -179,6 +183,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + @Override + public void execute(Runnable runnable) { ++ AsyncCatcher.catchOp("Callback Executor execute"); + if (queued == null) { + queued = new java.util.ArrayDeque<>(); + } +@@ -187,6 +192,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + @Override + public void run() { ++ AsyncCatcher.catchOp("Callback Executor run"); + if (queued == null) { + return; + } +@@ -341,6 +347,15 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.world = worldserver; + this.chunkGenerator = chunkgenerator; + this.executor = iasynctaskhandler; ++ // Paper start ++ this.mainInvokingExecutor = (run) -> { ++ if (MCUtil.isMainThread()) { ++ run.run(); ++ } else { ++ iasynctaskhandler.execute(run); ++ } ++ }; ++ // Paper end + ThreadedMailbox threadedmailbox = ThreadedMailbox.a(executor, "worldgen"); + + iasynctaskhandler.getClass(); +@@ -435,6 +450,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ checkHighPriorityChunks(player); + if (newState.size() != 1) { + return; + } +@@ -453,7 +469,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ); + PlayerChunkMap.this.world.getChunkProvider().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update +- }); ++ PlayerChunkMap.this.world.getChunkProvider().clearPriorityTickets(chunkPos); ++ }, (player, prevPos, newPos) -> { ++ player.lastHighPriorityChecked = -1; // reset and recheck ++ checkHighPriorityChunks(player); ++ }); + this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); + this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, +@@ -470,6 +490,115 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + }); + // Paper end - no-tick view distance + } ++ // Paper start - Chunk Prioritization ++ public void queueHolderUpdate(PlayerChunk playerchunk) { ++ Runnable runnable = () -> { ++ if (isUnloading(playerchunk)) { ++ return; // unloaded ++ } ++ chunkDistanceManager.pendingChunkUpdates.add(playerchunk); ++ if (!chunkDistanceManager.pollingPendingChunkUpdates) { ++ world.getChunkProvider().tickDistanceManager(); ++ } ++ }; ++ if (MCUtil.isMainThread()) { ++ // We can't use executor here because it will not execute tasks if its currently in the middle of executing tasks... ++ runnable.run(); ++ } else { ++ executor.execute(runnable); ++ } ++ } ++ ++ private boolean isUnloading(PlayerChunk playerchunk) { ++ return playerchunk == null || unloadQueue.contains(playerchunk.location.pair()); ++ } ++ ++ private void updateChunkPriorityMap(Long2IntOpenHashMap map, long chunk, int level) { ++ int prev = map.getOrDefault(chunk, -1); ++ if (level > prev) { ++ map.put(chunk, level); ++ } ++ } ++ ++ public void checkHighPriorityChunks(EntityPlayer player) { ++ int currentTick = MinecraftServer.currentTick; ++ if (currentTick - player.lastHighPriorityChecked < 20 || !player.isRealPlayer) { // weed out fake players ++ return; ++ } ++ player.lastHighPriorityChecked = currentTick; ++ Long2IntOpenHashMap priorities = new Long2IntOpenHashMap(); ++ ++ int viewDistance = getEffectiveNoTickViewDistance(); ++ BlockPosition.MutableBlockPosition pos = new BlockPosition.MutableBlockPosition(); ++ ++ // Prioritize circular near ++ double playerChunkX = MathHelper.floor(player.locX()) >> 4; ++ double playerChunkZ = MathHelper.floor(player.locZ()) >> 4; ++ pos.setValues(player.locX(), 0, player.locZ()); ++ double twoThirdModifier = 2D / 3D; ++ MCUtil.getSpiralOutChunks(pos, Math.min(6, viewDistance)).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) return; ++ ++ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); ++ // Prioritize immediate ++ if (dist <= 4) { ++ updateChunkPriorityMap(priorities, coord.pair(), (int) (27 - dist)); ++ return; ++ } ++ ++ // Prioritize nearby chunks ++ updateChunkPriorityMap(priorities, coord.pair(), (int) (20 - dist * twoThirdModifier)); ++ }); ++ ++ // Prioritize Frustum near 3 ++ ChunkCoordIntPair front3 = player.getChunkInFront(3); ++ pos.setValues(front3.x << 4, 0, front3.z << 4); ++ MCUtil.getSpiralOutChunks(pos, Math.min(5, viewDistance)).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) return; ++ ++ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); ++ updateChunkPriorityMap(priorities, coord.pair(), (int) (25 - dist * twoThirdModifier)); ++ }); ++ ++ // Prioritize Frustum near 5 ++ if (viewDistance > 4) { ++ ChunkCoordIntPair front5 = player.getChunkInFront(5); ++ pos.setValues(front5.x << 4, 0, front5.z << 4); ++ MCUtil.getSpiralOutChunks(pos, 4).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) return; ++ ++ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); ++ updateChunkPriorityMap(priorities, coord.pair(), (int) (25 - dist * twoThirdModifier)); ++ }); ++ } ++ ++ // Prioritize Frustum far 7 ++ if (viewDistance > 6) { ++ ChunkCoordIntPair front7 = player.getChunkInFront(7); ++ pos.setValues(front7.x << 4, 0, front7.z << 4); ++ MCUtil.getSpiralOutChunks(pos, 3).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) { ++ return; ++ } ++ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); ++ updateChunkPriorityMap(priorities, coord.pair(), (int) (25 - dist * twoThirdModifier)); ++ }); ++ } ++ ++ if (priorities.isEmpty()) return; ++ chunkDistanceManager.delayDistanceManagerTick = true; ++ priorities.long2IntEntrySet().fastForEach(entry -> chunkDistanceManager.markHighPriority(new ChunkCoordIntPair(entry.getLongKey()), entry.getIntValue())); ++ chunkDistanceManager.delayDistanceManagerTick = false; ++ world.getChunkProvider().tickDistanceManager(); ++ ++ } ++ ++ private boolean shouldSkipPrioritization(ChunkCoordIntPair coord) { ++ if (playerViewDistanceNoTickMap.getObjectsInRange(coord.pair()) == null) return true; ++ PlayerChunk chunk = getUpdatingChunk(coord.pair()); ++ return chunk != null && (chunk.isFullChunkReady()); ++ } ++ // Paper end + + public void updatePlayerMobTypeMap(Entity entity) { + if (!this.world.paperConfig.perPlayerMobSpawns) { +@@ -599,6 +728,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + List>> list = Lists.newArrayList(); + int j = chunkcoordintpair.x; + int k = chunkcoordintpair.z; ++ PlayerChunk requestingNeighbor = getUpdatingChunk(chunkcoordintpair.pair()); // Paper + + for (int l = -i; l <= i; ++l) { + for (int i1 = -i; i1 <= i; ++i1) { +@@ -617,6 +747,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + ChunkStatus chunkstatus = (ChunkStatus) intfunction.apply(j1); + CompletableFuture> completablefuture = playerchunk.a(chunkstatus, this); ++ // Paper start ++ if (requestingNeighbor != null && requestingNeighbor != playerchunk && !completablefuture.isDone()) { ++ requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus); ++ completablefuture.thenAccept(either -> { ++ requestingNeighbor.onNeighborDone(playerchunk, chunkstatus, either.left().orElse(null)); ++ }); ++ } ++ // Paper end + + list.add(completablefuture); + } +@@ -1084,14 +1222,22 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + }; + + CompletableFuture chunkSaveFuture = this.world.asyncChunkTaskManager.getChunkSaveFuture(chunkcoordintpair.x, chunkcoordintpair.z); ++ PlayerChunk playerChunk = getUpdatingChunk(chunkcoordintpair.pair()); ++ int chunkPriority = playerChunk != null ? playerChunk.getCurrentPriority() : 33; ++ int priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY; ++ ++ if (chunkPriority <= 10) { ++ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; ++ } else if (chunkPriority <= 20) { ++ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; ++ } ++ boolean isHighestPriority = priority == com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; + if (chunkSaveFuture != null) { +- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, +- com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture); +- this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY); ++ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isHighestPriority, chunkSaveFuture); + } else { +- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, +- com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false); ++ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isHighestPriority); + } ++ this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, priority); + return ret; + // Paper end + } +@@ -1228,7 +1374,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + long i = playerchunk.i().pair(); + + playerchunk.getClass(); +- mailbox.a(ChunkTaskQueueSorter.a(runnable, i, playerchunk::getTicketLevel)); ++ mailbox.a(ChunkTaskQueueSorter.a(runnable, i, () -> 1)); // Paper - final loads are always urgent! + }); + } + +diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java +index e06fe77f6ea05a93e95fce223bcfd0d16394f96f..90cbd12611b7b078f35f08f910453bcc02f6665b 100644 +--- a/src/main/java/net/minecraft/server/level/Ticket.java ++++ b/src/main/java/net/minecraft/server/level/Ticket.java +@@ -8,6 +8,7 @@ public final class Ticket implements Comparable> { + private final int b; + public final T identifier; public final T getObjectReason() { return this.identifier; } // Paper - OBFHELPER + private long d; public final long getCreationTick() { return this.d; } // Paper - OBFHELPER ++ public int priority = 0; // Paper + + protected Ticket(TicketType tickettype, int i, T t0) { + this.a = tickettype; +@@ -56,6 +57,7 @@ public final class Ticket implements Comparable> { + return this.b; + } + ++ public final void setCurrentTick(long i) { this.a(i); } // Paper - OBFHELPER + protected void a(long i) { + this.d = i; + } +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 2c932d36f982e7f8713aabff9a6c631055810366..f5d18834e0e2ee0e3bcf55810456766d2f134450 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -28,6 +28,8 @@ public class TicketType { + public static final TicketType PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType FUTURE_AWAIT = a("future_await", Long::compareTo); // Paper + public static final TicketType ASYNC_LOAD = a("async_load", Long::compareTo); // Paper ++ public static final TicketType PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper ++ public static final TicketType URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper + + public static TicketType a(String s, Comparator comparator) { + return new TicketType<>(s, comparator, 0L); +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 6db70005ebc99b19185b8efca550a0783ea05cad..42190a8c6c2eadf05f57df50e3ca997384327b67 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1529,6 +1529,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + + this.A = this.e; + this.player.setLocation(d0, d1, d2, f, f1); ++ this.player.forceCheckHighPriority(); // Paper + this.player.playerConnection.sendPacket(new PacketPlayOutPosition(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.teleportAwait)); + } + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index ee6de0186f6a112d02e1dd4cc73fdaa92ab4d0d9..65e01b98c74d2fa9ff6d6db18f9f8c684b7bdac4 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -275,8 +275,8 @@ public abstract class PlayerList { + final ChunkCoordIntPair pos = new ChunkCoordIntPair(chunkX, chunkZ); + PlayerChunkMap playerChunkMap = worldserver1.getChunkProvider().playerChunkMap; + playerChunkMap.getChunkDistanceManager().addTicketAtLevel(TicketType.LOGIN, pos, 31, pos.pair()); +- worldserver1.getChunkProvider().tickDistanceManager(); +- worldserver1.getChunkProvider().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> { ++ worldserver1.getChunkProvider().markAreaHighPriority(pos, 28, 3); ++ worldserver1.getChunkProvider().getChunkAtAsynchronously(chunkX, chunkZ, true, false).thenApply(chunk -> { + PlayerChunk updatingChunk = playerChunkMap.getUpdatingChunk(pos.pair()); + if (updatingChunk != null) { + return updatingChunk.getEntityTickingFuture(); +@@ -696,6 +696,7 @@ public abstract class PlayerList { + SocketAddress socketaddress = loginlistener.networkManager.getSocketAddress(); + + EntityPlayer entity = new EntityPlayer(this.server, this.server.getWorldServer(World.OVERWORLD), gameprofile, new PlayerInteractManager(this.server.getWorldServer(World.OVERWORLD))); ++ entity.isRealPlayer = true; // Paper + Player player = entity.getBukkitEntity(); + PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.networkManager.getRawAddress()).getAddress()); + +@@ -902,6 +903,7 @@ public abstract class PlayerList { + // CraftBukkit end + + worldserver1.getChunkProvider().addTicket(TicketType.POST_TELEPORT, new ChunkCoordIntPair(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper ++ entityplayer1.forceCheckHighPriority(); // Player + while (avoidSuffocation && !worldserver1.getCubes(entityplayer1) && entityplayer1.locY() < 256.0D) { + entityplayer1.setPosition(entityplayer1.locX(), entityplayer1.locY() + 1.0D, entityplayer1.locZ()); + } +diff --git a/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java b/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java +index 9a88791be443a5b18934e7d752aee6dcdb8aa38f..e41d63596c32eee5f0c04a6f043d576d8021ff1a 100644 +--- a/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java ++++ b/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java +@@ -104,6 +104,7 @@ public class ChunkCoordIntPair { + return "[" + this.x + ", " + this.z + "]"; + } + ++ public final BlockPosition asPosition() { return l(); } // Paper - OBFHELPER + public BlockPosition l() { + return new BlockPosition(this.d(), 0, this.e()); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 400499f32c59dcb3e850f5b52d84d4564c42c033..2511c44cc62b7cdce17741c786a58f73f4252955 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2541,6 +2541,10 @@ public class CraftWorld implements World { + return future; + } + ++ if (!urgent) { ++ // if not urgent, at least use a slightly boosted priority ++ world.getChunkProvider().markHighPriority(new ChunkCoordIntPair(x, z), 1); ++ } + return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { + net.minecraft.world.level.chunk.Chunk chunk = (net.minecraft.world.level.chunk.Chunk) either.left().orElse(null); + return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 7a240739ac6e043e590b380659d8e6d794954f84..ba8c36c9fa7a1fdc3047b14042da8703802f9275 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -60,6 +60,7 @@ import net.minecraft.server.level.PlayerChunkMap; + import net.minecraft.server.level.WorldServer; + import net.minecraft.server.network.PlayerConnection; + import net.minecraft.server.players.WhiteListEntry; ++import net.minecraft.util.MathHelper; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityExperienceOrb; + import net.minecraft.world.entity.EntityLiving; +@@ -72,6 +73,7 @@ import net.minecraft.world.inventory.Container; + import net.minecraft.world.item.EnumColor; + import net.minecraft.world.item.enchantment.EnchantmentManager; + import net.minecraft.world.item.enchantment.Enchantments; ++import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.EnumGamemode; + import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.block.entity.TileEntitySign; +@@ -853,6 +855,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead."); + } + ++ // Paper start ++ @Override ++ public java.util.concurrent.CompletableFuture teleportAsync(Location loc, @javax.annotation.Nonnull PlayerTeleportEvent.TeleportCause cause) { ++ ((CraftWorld)loc.getWorld()).getHandle().getChunkProvider().markAreaHighPriority(new ChunkCoordIntPair(MathHelper.floor(loc.getX()) >> 4, MathHelper.floor(loc.getZ()) >> 4), 28, 3); // Paper - load area high priority ++ return super.teleportAsync(loc, cause); ++ } ++ // Paper end ++ + @Override + public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { + Preconditions.checkArgument(location != null, "location"); diff --git a/patches/server-unmapped/0001/0488-Optimize-sending-packets-to-nearby-locations-sounds-.patch b/patches/server-unmapped/0001/0488-Optimize-sending-packets-to-nearby-locations-sounds-.patch new file mode 100644 index 0000000000..3a4024e267 --- /dev/null +++ b/patches/server-unmapped/0001/0488-Optimize-sending-packets-to-nearby-locations-sounds-.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 23 May 2020 17:03:41 -0400 +Subject: [PATCH] Optimize sending packets to nearby locations (sounds/effects) + +Instead of using the entire world or player list, use the distance +maps to only iterate players who are even seeing the chunk the packet +is originating from. + +This will drastically cut down on packet sending cost for worlds with +lots of players in them. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 65e01b98c74d2fa9ff6d6db18f9f8c684b7bdac4..fa9fae3979caaaef16a21d7407bb05e796963d7c 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1153,16 +1153,40 @@ public abstract class PlayerList { + } + + public void sendPacketNearby(@Nullable EntityHuman entityhuman, double d0, double d1, double d2, double d3, ResourceKey resourcekey, Packet packet) { +- for (int i = 0; i < this.players.size(); ++i) { +- EntityPlayer entityplayer = (EntityPlayer) this.players.get(i); ++ WorldServer world = null; ++ if (entityhuman != null && entityhuman.world instanceof WorldServer) { ++ world = (WorldServer) entityhuman.world; ++ } + +- // CraftBukkit start - Test if player receiving packet can see the source of the packet +- if (entityhuman != null && entityhuman instanceof EntityPlayer && !entityplayer.getBukkitEntity().canSee(((EntityPlayer) entityhuman).getBukkitEntity())) { +- continue; ++ // Paper start ++ if (world == null) { ++ world = server.getWorldServer(resourcekey); ++ } ++ PlayerChunkMap chunkMap = world != null ? world.getChunkProvider().playerChunkMap : null; ++ Object[] backingSet; ++ if (chunkMap == null) { ++ // Really shouldn't happen... ++ backingSet = world != null ? world.players.toArray() : players.toArray(); ++ } else { ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearbyPlayers = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.fastFloor(d0) >> 4, MCUtil.fastFloor(d2) >> 4); ++ if (nearbyPlayers == null) { ++ return; + } ++ backingSet = nearbyPlayers.getBackingSet(); ++ } ++ ++ for (Object object : backingSet) { ++ if (!(object instanceof EntityPlayer)) continue; ++ EntityPlayer entityplayer = (EntityPlayer) object; ++ // Paper end ++ ++ // CraftBukkit start - Test if player receiving packet can see the source of the packet ++ //if (entityhuman != null && entityhuman instanceof EntityPlayer && !entityplayer.getBukkitEntity().canSee(((EntityPlayer) entityhuman).getBukkitEntity())) { // Paper ++ //continue; // Paper ++ //} // Paper + // CraftBukkit end + +- if (entityplayer != entityhuman && entityplayer.world.getDimensionKey() == resourcekey) { ++ if (entityplayer != entityhuman && entityplayer.world.getDimensionKey() == resourcekey && (!(entityhuman instanceof EntityPlayer) || entityplayer.getBukkitEntity().canSee(((EntityPlayer) entityhuman).getBukkitEntity()))) { // Paper + double d4 = d0 - entityplayer.locX(); + double d5 = d1 - entityplayer.locY(); + double d6 = d2 - entityplayer.locZ(); diff --git a/patches/server-unmapped/0001/0489-Improve-Chunk-Status-Transition-Speed.patch b/patches/server-unmapped/0001/0489-Improve-Chunk-Status-Transition-Speed.patch new file mode 100644 index 0000000000..4df93b2bf2 --- /dev/null +++ b/patches/server-unmapped/0001/0489-Improve-Chunk-Status-Transition-Speed.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 May 2020 23:32:14 -0400 +Subject: [PATCH] Improve Chunk Status Transition Speed + +When a chunk is loaded from disk that has already been generated, +the server has to promote the chunk through the system to reach +it's current desired status level. + +This results in every single status transition going from the main thread +to the world gen threads, only to discover it has no work it actually +needs to do.... and then it returns back to main. + +This back and forth costs a lot of time and can really delay chunk loads +when the server is under high TPS due to their being a lot of time in +between chunk load times, as well as hogs up the chunk threads from doing +actual generation and light work. + +Additionally, the whole task system uses a lot of CPU on the server threads anyways. + +So by optimizing status transitions for status's that are already complete, +we can run them to the desired level while on main thread (where it has +to happen anyways) instead of ever jumping to world gen thread. + +This will improve chunk loading effeciency to be reduced down to the following +scenario / path: + +1) MAIN: Chunk Requested, Load Request sent to ChunkTaskManager / IO Queue +2) IO: Once position in queue comes, submit read IO data and schedule to chunk task thread +3) CHUNK: Once IO is loaded and position in queue comes, deserialize the chunk data, process conversions, submit to main queue +4) MAIN: next Chunk Task process (Mid Tick or End Of Tick), load chunk data into world (POI, main thread tasks) +5) MAIN: process status transitions all the way to LIGHT, light schedules Threaded task +6) SERVER: Light tasks register light enablement for chunk and any lighting needing to be done +7) MAIN: Task returns to main, finish processing to FULL/TICKING status + +Previously would have hopped to SERVER around 12+ times there extra. + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index fc79a73c884ceb7e0ce56443c36b135c4e525193..88022e3ccd04f9c041ced68be66a95247c1017e9 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -84,6 +84,13 @@ public class PlayerChunk { + this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); + } + // Paper end - optimise isOutsideOfRange ++ // Paper start - optimize chunk status progression without jumping through thread pool ++ public boolean canAdvanceStatus() { ++ ChunkStatus status = getChunkHolderStatus(); ++ IChunkAccess chunk = getAvailableChunkNow(); ++ return chunk != null && (status == null || chunk.getChunkStatus().isAtLeastStatus(getNextStatus(status))); ++ } ++ // Paper end + + // Paper start - no-tick view distance + public final Chunk getSendingChunk() { +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 01cce21eeed25b2bb36a0f32b9708afb83690f90..7318103feafd12ed631f907a450c9dc3d665a9a3 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -795,7 +795,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return either.mapLeft((list) -> { + return (Chunk) list.get(list.size() / 2); + }); +- }, this.executor); ++ }, this.mainInvokingExecutor); // Paper + } + + @Nullable +@@ -1145,7 +1145,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + IChunkAccess ichunkaccess = (IChunkAccess) optional.get(); + + if (ichunkaccess.getChunkStatus().b(chunkstatus)) { +- CompletableFuture completablefuture1; ++ CompletableFuture> completablefuture1; // Paper + + if (chunkstatus == ChunkStatus.LIGHT) { + completablefuture1 = this.b(playerchunk, chunkstatus); +@@ -1161,7 +1161,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return this.b(playerchunk, chunkstatus); + } + } +- }, this.executor); ++ }, this.mainInvokingExecutor).thenComposeAsync(CompletableFuture::completedFuture, this.mainInvokingExecutor); // Paper - optimize chunk status progression without jumping through thread pool - ensure main + } + } + +@@ -1282,6 +1282,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); + }); + }, (runnable) -> { ++ // Paper start - optimize chunk status progression without jumping through thread pool ++ if (playerchunk.canAdvanceStatus()) { ++ this.mainInvokingExecutor.execute(runnable); ++ return; ++ } ++ // Paper end + this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); + }); + } diff --git a/patches/server-unmapped/0001/0490-Fix-villager-trading-demand-MC-163962.patch b/patches/server-unmapped/0001/0490-Fix-villager-trading-demand-MC-163962.patch new file mode 100644 index 0000000000..b6b2585f20 --- /dev/null +++ b/patches/server-unmapped/0001/0490-Fix-villager-trading-demand-MC-163962.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Fri, 5 Jun 2020 20:02:04 -0500 +Subject: [PATCH] Fix villager trading demand - MC-163962 + +Prevent demand from going negative and tending to negative infinity + +diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantRecipe.java b/src/main/java/net/minecraft/world/item/trading/MerchantRecipe.java +index d840c657a6a992c86364a5f4536da0b217515c53..9e2fe0d5e6d4ea1f4c9cea96b755ddcd1e3c9009 100644 +--- a/src/main/java/net/minecraft/world/item/trading/MerchantRecipe.java ++++ b/src/main/java/net/minecraft/world/item/trading/MerchantRecipe.java +@@ -109,7 +109,7 @@ public class MerchantRecipe { + } + + public void e() { +- this.demand = this.demand + this.uses - (this.maxUses - this.uses); ++ this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper + } + + public ItemStack f() { diff --git a/patches/server-unmapped/0001/0491-Maps-shouldn-t-load-chunks.patch b/patches/server-unmapped/0001/0491-Maps-shouldn-t-load-chunks.patch new file mode 100644 index 0000000000..28c169e754 --- /dev/null +++ b/patches/server-unmapped/0001/0491-Maps-shouldn-t-load-chunks.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sun, 7 Jun 2020 21:43:42 +0100 +Subject: [PATCH] Maps shouldn't load chunks + +Previously maps would load all chunks in a certain radius depending on + their scale when trying to update their content. This would result in + main thread chunk loads when they weren't really necessary, especially + on low view distances or "slow" async chunk loads after teleports or + other prioritisation. + + This changes it to only try to render already loaded chunks based on + the assumption that the chunks around the player will get loaded + eventually anyways and that maps will get checked for update every + five ticks that movement occur in anyways. + +diff --git a/src/main/java/net/minecraft/world/item/ItemWorldMap.java b/src/main/java/net/minecraft/world/item/ItemWorldMap.java +index b18149cf048e78fefc019b50ed8f20ff8b609f5c..a1945e9ac1dd8961c5758a22bef3908d3adf0704 100644 +--- a/src/main/java/net/minecraft/world/item/ItemWorldMap.java ++++ b/src/main/java/net/minecraft/world/item/ItemWorldMap.java +@@ -120,9 +120,9 @@ public class ItemWorldMap extends ItemWorldMapBase { + int k2 = (j / i + k1 - 64) * i; + int l2 = (k / i + l1 - 64) * i; + Multiset multiset = LinkedHashMultiset.create(); +- Chunk chunk = world.getChunkAtWorldCoords(new BlockPosition(k2, 0, l2)); ++ Chunk chunk = world.getChunkIfLoaded(new BlockPosition(k2, 0, l2)); // Paper - Maps shouldn't load chunks + +- if (!chunk.isEmpty()) { ++ if (chunk != null && !chunk.isEmpty()) { // Paper - Maps shouldn't load chunks + ChunkCoordIntPair chunkcoordintpair = chunk.getPos(); + int i3 = k2 & 15; + int j3 = l2 & 15; diff --git a/patches/server-unmapped/0001/0492-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch b/patches/server-unmapped/0001/0492-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch new file mode 100644 index 0000000000..8e7eb59958 --- /dev/null +++ b/patches/server-unmapped/0001/0492-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 7 Jun 2020 19:25:13 -0400 +Subject: [PATCH] Use seed based lookup for Treasure Maps - Fixes lag from + carto/sunken maps + + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 06fa9b91cc103a5d5f39ab8fcfb5ccad4cf0e5de..1a7d06d8a3d1fe0a2a943eae5efd23d28fe4bd62 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -421,8 +421,8 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.worldDataServer.setThundering(flag1); + } + +- @Override +- public BiomeBase a(int i, int j, int k) { ++ public BiomeBase getBiomeBySeed(int i, int j, int k) { return a(i, j, k); } // Paper - OBFHELPER ++ @Override public BiomeBase a(int i, int j, int k) { + return this.getChunkProvider().getChunkGenerator().getWorldChunkManager().getBiome(i, j, k); + } + +diff --git a/src/main/java/net/minecraft/world/item/ItemWorldMap.java b/src/main/java/net/minecraft/world/item/ItemWorldMap.java +index a1945e9ac1dd8961c5758a22bef3908d3adf0704..3aa0f19d4a924d40005a38bb95a08d4a109c5b2e 100644 +--- a/src/main/java/net/minecraft/world/item/ItemWorldMap.java ++++ b/src/main/java/net/minecraft/world/item/ItemWorldMap.java +@@ -253,7 +253,7 @@ public class ItemWorldMap extends ItemWorldMapBase { + + for (l = 0; l < 128 * i; ++l) { + for (i1 = 0; i1 < 128 * i; ++i1) { +- abiomebase[l * 128 * i + i1] = worldserver.getBiome(new BlockPosition((j / i - 64) * i + i1, 0, (k / i - 64) * i + l)); ++ abiomebase[l * 128 * i + i1] = worldserver.getBiomeBySeed((j / i - 64) * i + i1, 0, (k / i - 64) * i + l); // Paper + } + } + diff --git a/patches/server-unmapped/0001/0493-Optimize-Bit-Operations-by-inlining.patch b/patches/server-unmapped/0001/0493-Optimize-Bit-Operations-by-inlining.patch new file mode 100644 index 0000000000..3e547b0e89 --- /dev/null +++ b/patches/server-unmapped/0001/0493-Optimize-Bit-Operations-by-inlining.patch @@ -0,0 +1,220 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 4 Jun 2020 02:24:49 -0400 +Subject: [PATCH] Optimize Bit Operations by inlining + +Inline bit operations and reduce instruction count to make these hot +operations faster + +diff --git a/src/main/java/net/minecraft/core/BlockPosition.java b/src/main/java/net/minecraft/core/BlockPosition.java +index 1fb931d4c0720a5e496030e25c865771aea3ec70..eb67af795dd716d9f92ac32843accc1ec4efd647 100644 +--- a/src/main/java/net/minecraft/core/BlockPosition.java ++++ b/src/main/java/net/minecraft/core/BlockPosition.java +@@ -31,14 +31,16 @@ public class BlockPosition extends BaseBlockPosition { + }).stable(); + private static final Logger LOGGER = LogManager.getLogger(); + public static final BlockPosition ZERO = new BlockPosition(0, 0, 0); +- private static final int f = 1 + MathHelper.f(MathHelper.c(30000000)); +- private static final int g = BlockPosition.f; +- private static final int h = 64 - BlockPosition.f - BlockPosition.g; +- private static final long i = (1L << BlockPosition.f) - 1L; +- private static final long j = (1L << BlockPosition.h) - 1L; +- private static final long k = (1L << BlockPosition.g) - 1L; +- private static final int l = BlockPosition.h; +- private static final int m = BlockPosition.h + BlockPosition.g; ++ // Paper start - static constants ++ private static final int f = 26; ++ private static final int g = 26; ++ private static final int h = 12; ++ private static final long i = 67108863; ++ private static final long j = 4095; ++ private static final long k = 67108863; ++ private static final int l = 12; ++ private static final int m = 38; ++ // Paper end + + public BlockPosition(int i, int j, int k) { + super(i, j, k); +@@ -60,28 +62,29 @@ public class BlockPosition extends BaseBlockPosition { + this(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); + } + ++ public static long getAdjacent(int baseX, int baseY, int baseZ, EnumDirection enumdirection) { return asLong(baseX + enumdirection.getAdjacentX(), baseY + enumdirection.getAdjacentY(), baseZ + enumdirection.getAdjacentZ()); } // Paper + public static long a(long i, EnumDirection enumdirection) { + return a(i, enumdirection.getAdjacentX(), enumdirection.getAdjacentY(), enumdirection.getAdjacentZ()); + } + + public static long a(long i, int j, int k, int l) { +- return a(b(i) + j, c(i) + k, d(i) + l); ++ return a((int) (i >> 38) + j, (int) ((i << 52) >> 52) + k, (int) ((i << 26) >> 38) + l); // Paper - simplify/inline + } + + public static int b(long i) { +- return (int) (i << 64 - BlockPosition.m - BlockPosition.f >> 64 - BlockPosition.f); ++ return (int) (i >> 38); // Paper - simplify/inline + } + + public static int c(long i) { +- return (int) (i << 64 - BlockPosition.h >> 64 - BlockPosition.h); ++ return (int) ((i << 52) >> 52); // Paper - simplify/inline + } + + public static int d(long i) { +- return (int) (i << 64 - BlockPosition.l - BlockPosition.g >> 64 - BlockPosition.g); ++ return (int) ((i << 26) >> 38); // Paper - simplify/inline + } + + public static BlockPosition fromLong(long i) { +- return new BlockPosition(b(i), c(i), d(i)); ++ return new BlockPosition((int) (i >> 38), (int) ((i << 52) >> 52), (int) ((i << 26) >> 38)); // Paper - simplify/inline + } + + public long asLong() { +@@ -90,12 +93,7 @@ public class BlockPosition extends BaseBlockPosition { + + public static long asLong(int x, int y, int z) { return a(x, y, z); } // Paper - OBFHELPER + public static long a(int i, int j, int k) { +- long l = 0L; +- +- l |= ((long) i & BlockPosition.i) << BlockPosition.m; +- l |= ((long) j & BlockPosition.j) << 0; +- l |= ((long) k & BlockPosition.k) << BlockPosition.l; +- return l; ++ return (((long) i & (long) 67108863) << 38) | (((long) j & (long) 4095)) | (((long) k & (long) 67108863) << 12); // Paper - inline constants and simplify + } + + public static long f(long i) { +diff --git a/src/main/java/net/minecraft/core/SectionPosition.java b/src/main/java/net/minecraft/core/SectionPosition.java +index 97126ae5a43bb7acb04a1ab14fb7f364c8c2675f..7d9a16eb81288b74425319c60525f57c98ad3b69 100644 +--- a/src/main/java/net/minecraft/core/SectionPosition.java ++++ b/src/main/java/net/minecraft/core/SectionPosition.java +@@ -19,7 +19,7 @@ public class SectionPosition extends BaseBlockPosition { + } + + public static SectionPosition a(BlockPosition blockposition) { +- return new SectionPosition(a(blockposition.getX()), a(blockposition.getY()), a(blockposition.getZ())); ++ return new SectionPosition(blockposition.getX() >> 4, blockposition.getY() >> 4, blockposition.getZ() >> 4); // Paper + } + + public static SectionPosition a(ChunkCoordIntPair chunkcoordintpair, int i) { +@@ -31,15 +31,23 @@ public class SectionPosition extends BaseBlockPosition { + } + + public static SectionPosition a(long i) { +- return new SectionPosition(b(i), c(i), d(i)); ++ return new SectionPosition((int) (i >> 42), (int) (i << 44 >> 44), (int) (i << 22 >> 42)); // Paper + } + + public static long a(long i, EnumDirection enumdirection) { + return a(i, enumdirection.getAdjacentX(), enumdirection.getAdjacentY(), enumdirection.getAdjacentZ()); + } + ++ // Paper start ++ public static long getAdjacentFromBlockPos(int x, int y, int z, EnumDirection enumdirection) { ++ return (((long) ((x >> 4) + enumdirection.getAdjacentX()) & 4194303L) << 42) | (((long) ((y >> 4) + enumdirection.getAdjacentY()) & 1048575L)) | (((long) ((z >> 4) + enumdirection.getAdjacentZ()) & 4194303L) << 20); ++ } ++ public static long getAdjacentFromSectionPos(int x, int y, int z, EnumDirection enumdirection) { ++ return (((long) (x + enumdirection.getAdjacentX()) & 4194303L) << 42) | (((long) ((y) + enumdirection.getAdjacentY()) & 1048575L)) | (((long) (z + enumdirection.getAdjacentZ()) & 4194303L) << 20); ++ } ++ // Paper end + public static long a(long i, int j, int k, int l) { +- return b(b(i) + j, c(i) + k, d(i) + l); ++ return (((long) ((int) (i >> 42) + j) & 4194303L) << 42) | (((long) ((int) (i << 44 >> 44) + k) & 1048575L)) | (((long) ((int) (i << 22 >> 42) + l) & 4194303L) << 20); // Simplify to reduce instruction count + } + + public static int a(int i) { +@@ -51,11 +59,7 @@ public class SectionPosition extends BaseBlockPosition { + } + + public static short b(BlockPosition blockposition) { +- int i = b(blockposition.getX()); +- int j = b(blockposition.getY()); +- int k = b(blockposition.getZ()); +- +- return (short) (i << 8 | k << 4 | j << 0); ++ return (short) ((blockposition.getX() & 15) << 8 | (blockposition.getZ() & 15) << 4 | blockposition.getY() & 15); // Paper - simplify/inline + } + + public static int a(short short0) { +@@ -114,16 +118,16 @@ public class SectionPosition extends BaseBlockPosition { + return this.getZ(); + } + +- public int d() { +- return this.a() << 4; ++ public final int d() { // Paper ++ return this.getX() << 4; // Paper + } + +- public int e() { +- return this.b() << 4; ++ public final int e() { // Paper ++ return this.getY() << 4; // Paper + } + +- public int f() { +- return this.c() << 4; ++ public final int f() { // Paper ++ return this.getZ() << 4; // Paper + } + + public int g() { +@@ -138,8 +142,10 @@ public class SectionPosition extends BaseBlockPosition { + return (this.c() << 4) + 15; + } + ++ public static long blockToSection(long i) { return e(i); } // Paper - OBFHELPER + public static long e(long i) { +- return b(a(BlockPosition.b(i)), a(BlockPosition.c(i)), a(BlockPosition.d(i))); ++ // b(a(BlockPosition.b(i)), a(BlockPosition.c(i)), a(BlockPosition.d(i))); ++ return (((long) (int) (i >> 42) & 4194303L) << 42) | (((long) (int) ((i << 52) >> 56) & 1048575L)) | (((long) (int) ((i << 26) >> 42) & 4194303L) << 20); // Simplify to reduce instruction count + } + + public static long f(long i) { +@@ -160,17 +166,18 @@ public class SectionPosition extends BaseBlockPosition { + return new ChunkCoordIntPair(this.a(), this.c()); + } + ++ // Paper start ++ public static long blockPosAsSectionLong(int i, int j, int k) { ++ return (((long) (i >> 4) & 4194303L) << 42) | (((long) (j >> 4) & 1048575L)) | (((long) (k >> 4) & 4194303L) << 20); ++ } ++ // Paper end ++ public static long asLong(int i, int j, int k) { return b(i, j, k); } // Paper - OBFHELPER + public static long b(int i, int j, int k) { +- long l = 0L; +- +- l |= ((long) i & 4194303L) << 42; +- l |= ((long) j & 1048575L) << 0; +- l |= ((long) k & 4194303L) << 20; +- return l; ++ return (((long) i & 4194303L) << 42) | (((long) j & 1048575L)) | (((long) k & 4194303L) << 20); // Paper - Simplify to reduce instruction count + } + + public long s() { +- return b(this.a(), this.b(), this.c()); ++ return (((long) getX() & 4194303L) << 42) | (((long) getY() & 1048575L)) | (((long) getZ() & 4194303L) << 20); // Paper - Simplify to reduce instruction count + } + + public Stream t() { +@@ -178,18 +185,11 @@ public class SectionPosition extends BaseBlockPosition { + } + + public static Stream a(SectionPosition sectionposition, int i) { +- int j = sectionposition.a(); +- int k = sectionposition.b(); +- int l = sectionposition.c(); +- +- return a(j - i, k - i, l - i, j + i, k + i, l + i); ++ return a(sectionposition.getX() - i, sectionposition.getY() - i, sectionposition.getZ() - i, sectionposition.getX() + i, sectionposition.getY() + i, sectionposition.getZ() + i); // Paper - simplify/inline + } + + public static Stream b(ChunkCoordIntPair chunkcoordintpair, int i) { +- int j = chunkcoordintpair.x; +- int k = chunkcoordintpair.z; +- +- return a(j - i, 0, k - i, j + i, 15, k + i); ++ return a(chunkcoordintpair.x - i, 0, chunkcoordintpair.z - i, chunkcoordintpair.x + i, 15, chunkcoordintpair.z + i); // Paper - simplify/inline + } + + public static Stream a(final int i, final int j, final int k, final int l, final int i1, final int j1) { diff --git a/patches/server-unmapped/0001/0494-Optimize-Light-Engine.patch b/patches/server-unmapped/0001/0494-Optimize-Light-Engine.patch new file mode 100644 index 0000000000..b519a242b2 --- /dev/null +++ b/patches/server-unmapped/0001/0494-Optimize-Light-Engine.patch @@ -0,0 +1,1433 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 4 Jun 2020 22:43:29 -0400 +Subject: [PATCH] Optimize Light Engine + +Massive update to light to improve performance and chunk loading/generation. + +1) Massive bit packing/unpacking optimizations and inlining. + A lot of performance has to do with constant packing and unpacking of bits. + We now inline a most bit operations, and re-use base x/y/z bits in many places. + This helps with cpu level processing to just do all the math at once instead + of having to jump in and out of function calls. + + This much logic also is likely over the JVM Inline limit for JIT too. +2) Applied a few of JellySquid's Phosphor mod optimizations such as + - ensuring we don't notify neighbor chunks when neighbor chunk doesn't need to be notified + - reduce hasLight checks in initializing light, and prob some more, they are tagged JellySquid where phosphor influence was used. +3) Optimize hot path accesses to getting updating chunk to have less branching +4) Optimize getBlock accesses to have less branching, and less unpacking +5) Have a separate urgent bucket for chunk light tasks. These tasks will always cut in line over non blocking light tasks. +6) Retain chunk priority while light tasks are enqueued. So if a task comes in at high priority but the queue is full + of tasks already at a lower priority, before the task was simply added to the end. Now it can cut in line to the front. + this applies for both urgent and non urgent tasks. +7) Buffer non urgent tasks even if queueUpdate is called multiple times to improve efficiency. +8) Fix NPE risk that crashes server in getting nibble data + +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index 8acb8ef15cfe9c54f56c1c8da02940ab32ceb8c5..e456ac2c1d57dcf6ad7dffb7f1ed45be7411dedb 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -1071,7 +1071,7 @@ public class ChunkProviderServer extends IChunkProvider { + if (ChunkProviderServer.this.tickDistanceManager()) { + return true; + } else { +- ChunkProviderServer.this.lightEngine.queueUpdate(); ++ ChunkProviderServer.this.lightEngine.queueUpdate(); // Paper - not needed + return super.executeNext() || execChunkTask; // Paper + } + } finally { +diff --git a/src/main/java/net/minecraft/server/level/LightEngineGraphSection.java b/src/main/java/net/minecraft/server/level/LightEngineGraphSection.java +index c8bb040e7ed848877ec9c2f9b30dcda137cadf35..4ee7070364a8989eece4fa4237b529926821f9c9 100644 +--- a/src/main/java/net/minecraft/server/level/LightEngineGraphSection.java ++++ b/src/main/java/net/minecraft/server/level/LightEngineGraphSection.java +@@ -16,14 +16,20 @@ public abstract class LightEngineGraphSection extends LightEngineGraph { + + @Override + protected void a(long i, int j, boolean flag) { ++ // Paper start ++ int x = (int) (i >> 42); ++ int y = (int) (i << 44 >> 44); ++ int z = (int) (i << 22 >> 42); ++ // Paper end + for (int k = -1; k <= 1; ++k) { + for (int l = -1; l <= 1; ++l) { + for (int i1 = -1; i1 <= 1; ++i1) { +- long j1 = SectionPosition.a(i, k, l, i1); ++ if (k == 0 && l == 0 && i1 == 0) continue; // Paper ++ long j1 = (((long) (x + k) & 4194303L) << 42) | (((long) (y + l) & 1048575L)) | (((long) (z + i1) & 4194303L) << 20); // Paper + +- if (j1 != i) { ++ //if (j1 != i) { // Paper - checked above + this.b(i, j1, j, flag); +- } ++ //} // Paper + } + } + } +@@ -34,10 +40,15 @@ public abstract class LightEngineGraphSection extends LightEngineGraph { + protected int a(long i, long j, int k) { + int l = k; + ++ // Paper start ++ int x = (int) (i >> 42); ++ int y = (int) (i << 44 >> 44); ++ int z = (int) (i << 22 >> 42); ++ // Paper end + for (int i1 = -1; i1 <= 1; ++i1) { + for (int j1 = -1; j1 <= 1; ++j1) { + for (int k1 = -1; k1 <= 1; ++k1) { +- long l1 = SectionPosition.a(i, i1, j1, k1); ++ long l1 = (((long) (x + i1) & 4194303L) << 42) | (((long) (y + j1) & 1048575L)) | (((long) (z + k1) & 4194303L) << 20); // Paper + + if (l1 == i) { + l1 = Long.MAX_VALUE; +diff --git a/src/main/java/net/minecraft/server/level/LightEngineThreaded.java b/src/main/java/net/minecraft/server/level/LightEngineThreaded.java +index e066848127cb9a42e8c39422691cc65132cac6bb..0b80569648c1df01aab52d0b8d47028cda925d86 100644 +--- a/src/main/java/net/minecraft/server/level/LightEngineThreaded.java ++++ b/src/main/java/net/minecraft/server/level/LightEngineThreaded.java +@@ -1,6 +1,7 @@ + package net.minecraft.server.level; + + import com.mojang.datafixers.util.Pair; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; // Paper + import it.unimi.dsi.fastutil.objects.ObjectArrayList; + import it.unimi.dsi.fastutil.objects.ObjectList; + import it.unimi.dsi.fastutil.objects.ObjectListIterator; +@@ -16,6 +17,7 @@ import net.minecraft.util.thread.ThreadedMailbox; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.EnumSkyBlock; + import net.minecraft.world.level.chunk.ChunkSection; ++import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.IChunkAccess; + import net.minecraft.world.level.chunk.ILightAccess; + import net.minecraft.world.level.chunk.NibbleArray; +@@ -27,15 +29,149 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + + private static final Logger LOGGER = LogManager.getLogger(); + private final ThreadedMailbox b; +- private final ObjectList> c = new ObjectArrayList(); +- private final PlayerChunkMap d; ++ // Paper start ++ private static final int MAX_PRIORITIES = PlayerChunkMap.GOLDEN_TICKET + 2; ++ ++ private boolean isChunkLightStatus(long pair) { ++ PlayerChunk playerChunk = playerChunkMap.getVisibleChunk(pair); ++ if (playerChunk == null) { ++ return false; ++ } ++ ChunkStatus status = PlayerChunk.getChunkStatus(playerChunk.getTicketLevel()); ++ return status != null && status.isAtLeastStatus(ChunkStatus.LIGHT); ++ } ++ ++ static class ChunkLightQueue { ++ public boolean shouldFastUpdate; ++ java.util.ArrayDeque pre = new java.util.ArrayDeque(); ++ java.util.ArrayDeque post = new java.util.ArrayDeque(); ++ ++ ChunkLightQueue(long chunk) {} ++ } ++ ++ static class PendingLightTask { ++ long chunkId; ++ IntSupplier priority; ++ Runnable pre; ++ Runnable post; ++ boolean fastUpdate; ++ ++ public PendingLightTask(long chunkId, IntSupplier priority, Runnable pre, Runnable post, boolean fastUpdate) { ++ this.chunkId = chunkId; ++ this.priority = priority; ++ this.pre = pre; ++ this.post = post; ++ this.fastUpdate = fastUpdate; ++ } ++ } ++ ++ ++ // Retain the chunks priority level for queued light tasks ++ class LightQueue { ++ private int size = 0; ++ private final Long2ObjectLinkedOpenHashMap[] buckets = new Long2ObjectLinkedOpenHashMap[MAX_PRIORITIES]; ++ private final java.util.concurrent.ConcurrentLinkedQueue pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private final java.util.concurrent.ConcurrentLinkedQueue priorityChanges = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ ++ private LightQueue() { ++ for (int i = 0; i < buckets.length; i++) { ++ buckets[i] = new Long2ObjectLinkedOpenHashMap<>(); ++ } ++ } ++ ++ public void changePriority(long pair, int currentPriority, int priority) { ++ this.priorityChanges.add(() -> { ++ ChunkLightQueue remove = this.buckets[currentPriority].remove(pair); ++ if (remove != null) { ++ ChunkLightQueue existing = this.buckets[Math.max(1, priority)].put(pair, remove); ++ if (existing != null) { ++ remove.pre.addAll(existing.pre); ++ remove.post.addAll(existing.post); ++ } ++ } ++ }); ++ } ++ ++ public final void addChunk(long chunkId, IntSupplier priority, Runnable pre, Runnable post) { ++ pendingTasks.add(new PendingLightTask(chunkId, priority, pre, post, true)); ++ queueUpdate(); ++ } ++ ++ public final void add(long chunkId, IntSupplier priority, LightEngineThreaded.Update type, Runnable run) { ++ pendingTasks.add(new PendingLightTask(chunkId, priority, type == Update.PRE_UPDATE ? run : null, type == Update.POST_UPDATE ? run : null, false)); ++ } ++ public final void add(PendingLightTask update) { ++ int priority = update.priority.getAsInt(); ++ ChunkLightQueue lightQueue = this.buckets[priority].computeIfAbsent(update.chunkId, ChunkLightQueue::new); ++ ++ if (update.pre != null) { ++ this.size++; ++ lightQueue.pre.add(update.pre); ++ } ++ if (update.post != null) { ++ this.size++; ++ lightQueue.post.add(update.post); ++ } ++ if (update.fastUpdate) { ++ lightQueue.shouldFastUpdate = true; ++ } ++ } ++ ++ public final boolean isEmpty() { ++ return this.size == 0 && this.pendingTasks.isEmpty(); ++ } ++ ++ public final int size() { ++ return this.size; ++ } ++ ++ public boolean poll(java.util.List pre, java.util.List post) { ++ PendingLightTask pending; ++ while ((pending = pendingTasks.poll()) != null) { ++ add(pending); ++ } ++ Runnable run; ++ while ((run = priorityChanges.poll()) != null) { ++ run.run(); ++ } ++ boolean hasWork = false; ++ Long2ObjectLinkedOpenHashMap[] buckets = this.buckets; ++ int priority = 0; ++ while (priority < MAX_PRIORITIES && !isEmpty()) { ++ Long2ObjectLinkedOpenHashMap bucket = buckets[priority]; ++ if (bucket.isEmpty()) { ++ priority++; ++ if (hasWork) { ++ return true; ++ } else { ++ continue; ++ } ++ } ++ ChunkLightQueue queue = bucket.removeFirst(); ++ this.size -= queue.pre.size() + queue.post.size(); ++ pre.addAll(queue.pre); ++ post.addAll(queue.post); ++ queue.pre.clear(); ++ queue.post.clear(); ++ hasWork = true; ++ if (queue.shouldFastUpdate) { ++ return true; ++ } ++ } ++ return hasWork; ++ } ++ } ++ ++ final LightQueue queue = new LightQueue(); ++ // Paper end ++ private final PlayerChunkMap d; private final PlayerChunkMap playerChunkMap; // Paper + private final Mailbox> e; + private volatile int f = 5; + private final AtomicBoolean g = new AtomicBoolean(); + + public LightEngineThreaded(ILightAccess ilightaccess, PlayerChunkMap playerchunkmap, boolean flag, ThreadedMailbox threadedmailbox, Mailbox> mailbox) { + super(ilightaccess, true, flag); +- this.d = playerchunkmap; ++ this.d = playerchunkmap; this.playerChunkMap = d; // Paper + this.e = mailbox; + this.b = threadedmailbox; + } +@@ -122,13 +258,9 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + } + + private void a(int i, int j, IntSupplier intsupplier, LightEngineThreaded.Update lightenginethreaded_update, Runnable runnable) { +- this.e.a(ChunkTaskQueueSorter.a(() -> { +- this.c.add(Pair.of(lightenginethreaded_update, runnable)); +- if (this.c.size() >= this.f) { +- this.b(); +- } +- +- }, ChunkCoordIntPair.pair(i, j), intsupplier)); ++ // Paper start - replace method ++ this.queue.add(ChunkCoordIntPair.pair(i, j), intsupplier, lightenginethreaded_update, runnable); ++ // Paper end + } + + @Override +@@ -145,8 +277,19 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + public CompletableFuture a(IChunkAccess ichunkaccess, boolean flag) { + ChunkCoordIntPair chunkcoordintpair = ichunkaccess.getPos(); + +- ichunkaccess.b(false); +- this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { ++ // Paper start ++ //ichunkaccess.b(false); // Don't need to disable this ++ long pair = chunkcoordintpair.pair(); ++ CompletableFuture future = new CompletableFuture<>(); ++ IntSupplier prioritySupplier = playerChunkMap.getPrioritySupplier(pair); ++ boolean[] skippedPre = {false}; ++ this.queue.addChunk(pair, prioritySupplier, SystemUtils.a(() -> { ++ if (!isChunkLightStatus(pair)) { ++ future.complete(ichunkaccess); ++ skippedPre[0] = true; ++ return; ++ } ++ // Paper end + ChunkSection[] achunksection = ichunkaccess.getSections(); + + for (int i = 0; i < 16; ++i) { +@@ -164,55 +307,48 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + }); + } + +- this.d.c(chunkcoordintpair); ++ // this.d.c(chunkcoordintpair); // Paper - move into post task below + }, () -> { + return "lightChunk " + chunkcoordintpair + " " + flag; +- })); +- return CompletableFuture.supplyAsync(() -> { ++ // Paper start - merge the 2 together ++ }), () -> { ++ this.d.c(chunkcoordintpair); // Paper - release light tickets as post task to ensure they stay loaded until fully done ++ if (skippedPre[0]) return; // Paper - future's already complete + ichunkaccess.b(true); + super.b(chunkcoordintpair, false); +- return ichunkaccess; +- }, (runnable) -> { +- this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.POST_UPDATE, runnable); ++ // Paper start ++ future.complete(ichunkaccess); + }); ++ return future; ++ // Paper end + } + + public void queueUpdate() { +- if ((!this.c.isEmpty() || super.a()) && this.g.compareAndSet(false, true)) { ++ if ((!this.queue.isEmpty() || super.a()) && this.g.compareAndSet(false, true)) { // Paper + this.b.a((() -> { // Paper - decompile error + this.b(); + this.g.set(false); ++ queueUpdate(); // Paper - if we still have work to do, do it! + })); + } + + } + ++ // Paper start - replace impl ++ private final java.util.List pre = new java.util.ArrayList<>(); ++ private final java.util.List post = new java.util.ArrayList<>(); + private void b() { +- int i = Math.min(this.c.size(), this.f); +- ObjectListIterator> objectlistiterator = this.c.iterator(); +- +- Pair pair; +- int j; +- +- for (j = 0; objectlistiterator.hasNext() && j < i; ++j) { +- pair = (Pair) objectlistiterator.next(); +- if (pair.getFirst() == LightEngineThreaded.Update.PRE_UPDATE) { +- ((Runnable) pair.getSecond()).run(); +- } ++ if (queue.poll(pre, post)) { ++ pre.forEach(Runnable::run); ++ pre.clear(); ++ super.a(Integer.MAX_VALUE, true, true); ++ post.forEach(Runnable::run); ++ post.clear(); ++ } else { ++ // might have level updates to go still ++ super.a(Integer.MAX_VALUE, true, true); + } +- +- objectlistiterator.back(j); +- super.a(Integer.MAX_VALUE, true, true); +- +- for (j = 0; objectlistiterator.hasNext() && j < i; ++j) { +- pair = (Pair) objectlistiterator.next(); +- if (pair.getFirst() == LightEngineThreaded.Update.POST_UPDATE) { +- ((Runnable) pair.getSecond()).run(); +- } +- +- objectlistiterator.remove(); +- } +- ++ // Paper end + } + + public void a(int i) { +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 88022e3ccd04f9c041ced68be66a95247c1017e9..d6a5a0b17308913a5efd97cd27fabd0825ef68c6 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -754,6 +754,7 @@ public class PlayerChunk { + ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; + } + chunkMap.world.asyncChunkTaskManager.raisePriority(location.x, location.z, ioPriority); ++ chunkMap.world.getChunkProvider().getLightEngine().queue.changePriority(location.pair(), getCurrentPriority(), priority); + } + if (getCurrentPriority() != priority) { + this.u.a(this.location, this::getCurrentPriority, priority, this::setPriority); // use preferred priority +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 7318103feafd12ed631f907a450c9dc3d665a9a3..b47cd2a8fb4920531d80acfcfe40f8211fedc9ae 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -99,6 +99,7 @@ import net.minecraft.world.level.chunk.storage.RegionFile; + import net.minecraft.world.level.levelgen.structure.StructureStart; + import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructureManager; + import net.minecraft.world.level.storage.Convertable; ++import net.minecraft.world.level.storage.WorldDataServer; + import net.minecraft.world.level.storage.WorldPersistentData; + import net.minecraft.world.phys.Vec3D; + import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper +@@ -331,6 +332,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + // Paper end + ++ private final java.util.concurrent.ExecutorService lightThread; + public PlayerChunkMap(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier supplier, int i, boolean flag) { + super(new File(convertable_conversionsession.a(worldserver.getDimensionKey()), "region"), datafixer, flag); + //this.visibleChunks = this.updatingChunks.clone(); // Paper - no more cloning +@@ -362,7 +364,15 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + Mailbox mailbox = Mailbox.a("main", iasynctaskhandler::a); + + this.worldLoadListener = worldloadlistener; +- ThreadedMailbox lightthreaded; ThreadedMailbox threadedmailbox1 = lightthreaded = ThreadedMailbox.a(executor, "light"); // Paper ++ // Paper start - use light thread ++ ThreadedMailbox lightthreaded; ThreadedMailbox threadedmailbox1 = lightthreaded = ThreadedMailbox.a(lightThread = java.util.concurrent.Executors.newSingleThreadExecutor(r -> { ++ Thread thread = new Thread(r); ++ thread.setName(((WorldDataServer)world.getWorldData()).getName() + " - Light"); ++ thread.setDaemon(true); ++ thread.setPriority(Thread.NORM_PRIORITY+1); ++ return thread; ++ }), "light"); ++ // Paper end + + this.p = new ChunkTaskQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE); + this.mailboxWorldGen = this.p.a(threadedmailbox, false); +@@ -708,6 +718,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + // Paper end + } + ++ protected final IntSupplier getPrioritySupplier(long i) { return c(i); } // Paper - OBFHELPER + protected IntSupplier c(long i) { + return () -> { + PlayerChunk playerchunk = this.getVisibleChunk(i); +@@ -835,6 +846,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + @Override + public void close() throws IOException { + try { ++ this.lightThread.shutdown(); // Paper + this.p.close(); + this.world.asyncChunkTaskManager.close(true); // Paper - Required since we're closing regionfiles in the next line + this.m.close(); +diff --git a/src/main/java/net/minecraft/util/thread/ThreadedMailbox.java b/src/main/java/net/minecraft/util/thread/ThreadedMailbox.java +index 2efca1fe92b2e93dcbf5337eea8855b1b2b9a564..72bfda620f073fd3c3e4c43d78583386dadf95e6 100644 +--- a/src/main/java/net/minecraft/util/thread/ThreadedMailbox.java ++++ b/src/main/java/net/minecraft/util/thread/ThreadedMailbox.java +@@ -110,7 +110,8 @@ public class ThreadedMailbox implements Mailbox, AutoCloseable, Runnable { + + } + +- @Override ++ ++ public final void queue(T t0) { a(t0); } @Override // Paper - OBFHELPER + public void a(T t0) { + this.a.a(t0); + this.f(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java b/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java +index b82420e9a5d42a4383d24921614fe613c640edb9..0fec15e141051863dbf51a2b3e1ace5028cd2fc1 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java ++++ b/src/main/java/net/minecraft/world/level/chunk/NibbleArray.java +@@ -11,6 +11,13 @@ import net.minecraft.server.MCUtil; + public class NibbleArray { + + // Paper start ++ public static final NibbleArray EMPTY_NIBBLE_ARRAY = new NibbleArray() { ++ @Override ++ public byte[] asBytes() { ++ throw new IllegalStateException(); ++ } ++ }; ++ public long lightCacheKey = Long.MIN_VALUE; + public static byte[] EMPTY_NIBBLE = new byte[2048]; + private static final int nibbleBucketSizeMultiplier = Integer.getInteger("Paper.nibbleBucketSize", 3072); + private static final int maxPoolSize = Integer.getInteger("Paper.maxNibblePoolSize", (int) Math.min(6, Math.max(1, Runtime.getRuntime().maxMemory() / 1024 / 1024 / 1024)) * (nibbleBucketSizeMultiplier * 8)); +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineBlock.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineBlock.java +index f6198069e3ca421b4f551939263c7cf8bd5b754e..29e98864209c51368a91fa9e530c33cbf9830b51 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineBlock.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineBlock.java +@@ -23,9 +23,11 @@ public final class LightEngineBlock extends LightEngineLayer> 38); ++ int k = (int) ((i << 52) >> 52); ++ int l = (int) ((i << 26) >> 38); ++ // Paper end + IBlockAccess iblockaccess = this.a.c(j >> 4, l >> 4); + + return iblockaccess != null ? iblockaccess.g(this.f.d(j, k, l)) : 0; +@@ -40,25 +42,33 @@ public final class LightEngineBlock extends LightEngineLayer= 15) { + return k; + } else { +- int l = Integer.signum(BlockPosition.b(j) - BlockPosition.b(i)); +- int i1 = Integer.signum(BlockPosition.c(j) - BlockPosition.c(i)); +- int j1 = Integer.signum(BlockPosition.d(j) - BlockPosition.d(i)); ++ // Paper start - reuse math - credit to JellySquid for idea ++ int jx = (int) (j >> 38); ++ int jy = (int) ((j << 52) >> 52); ++ int jz = (int) ((j << 26) >> 38); ++ int ix = (int) (i >> 38); ++ int iy = (int) ((i << 52) >> 52); ++ int iz = (int) ((i << 26) >> 38); ++ int l = Integer.signum(jx - ix); ++ int i1 = Integer.signum(jy - iy); ++ int j1 = Integer.signum(jz - iz); ++ // Paper end + EnumDirection enumdirection = EnumDirection.a(l, i1, j1); + + if (enumdirection == null) { + return 15; + } else { + //MutableInt mutableint = new MutableInt(); // Paper - share mutableint, single threaded +- IBlockData iblockdata = this.a(j, mutableint); +- +- if (mutableint.getValue() >= 15) { ++ IBlockData iblockdata = this.getBlockOptimized(jx, jy, jz, mutableint); // Paper ++ int blockedLight = mutableint.getValue(); // Paper ++ if (blockedLight >= 15) { // Paper + return 15; + } else { +- IBlockData iblockdata1 = this.a(i, (MutableInt) null); ++ IBlockData iblockdata1 = this.getBlockOptimized(ix, iy, iz); // Paper + VoxelShape voxelshape = this.a(iblockdata1, i, enumdirection); + VoxelShape voxelshape1 = this.a(iblockdata, j, enumdirection.opposite()); + +- return VoxelShapes.b(voxelshape, voxelshape1) ? 15 : k + Math.max(1, mutableint.getValue()); ++ return VoxelShapes.b(voxelshape, voxelshape1) ? 15 : k + Math.max(1, blockedLight); // Paper + } + } + } +@@ -66,14 +76,19 @@ public final class LightEngineBlock extends LightEngineLayer> 38); ++ int y = (int) ((i << 52) >> 52); ++ int z = (int) ((i << 26) >> 38); ++ long k = SectionPosition.blockPosAsSectionLong(x, y, z); ++ // Paper end + EnumDirection[] aenumdirection = LightEngineBlock.e; + int l = aenumdirection.length; + + for (int i1 = 0; i1 < l; ++i1) { + EnumDirection enumdirection = aenumdirection[i1]; +- long j1 = BlockPosition.a(i, enumdirection); +- long k1 = SectionPosition.e(j1); ++ long j1 = BlockPosition.getAdjacent(x, y, z, enumdirection); // Paper ++ long k1 = SectionPosition.blockToSection(j1); // Paper + + if (k == k1 || ((LightEngineStorageBlock) this.c).g(k1)) { + this.b(i, j1, j, flag); +@@ -98,27 +113,37 @@ public final class LightEngineBlock extends LightEngineLayer> 38); ++ int baseY = (int) ((i << 52) >> 52); ++ int baseZ = (int) ((i << 26) >> 38); ++ long j1 = SectionPosition.blockPosAsSectionLong(baseX, baseY, baseZ); ++ NibbleArray nibblearray = this.c.updating.getUpdatingOptimized(j1); ++ // Paper end + EnumDirection[] aenumdirection = LightEngineBlock.e; + int k1 = aenumdirection.length; + + for (int l1 = 0; l1 < k1; ++l1) { + EnumDirection enumdirection = aenumdirection[l1]; +- long i2 = BlockPosition.a(i, enumdirection); ++ // Paper start ++ int newX = baseX + enumdirection.getAdjacentX(); ++ int newY = baseY + enumdirection.getAdjacentY(); ++ int newZ = baseZ + enumdirection.getAdjacentZ(); ++ long i2 = BlockPosition.asLong(newX, newY, newZ); + + if (i2 != j) { +- long j2 = SectionPosition.e(i2); ++ long j2 = SectionPosition.blockPosAsSectionLong(newX, newY, newZ); ++ // Paper end + NibbleArray nibblearray1; + + if (j1 == j2) { + nibblearray1 = nibblearray; + } else { +- nibblearray1 = ((LightEngineStorageBlock) this.c).a(j2, true); ++ nibblearray1 = ((LightEngineStorageBlock) this.c).updating.getUpdatingOptimized(j2); // Paper + } + + if (nibblearray1 != null) { +- int k2 = this.b(i2, i, this.a(nibblearray1, i2)); ++ int k2 = this.b(i2, i, this.getNibbleLightInverse(nibblearray1, newX, newY, newZ)); // Paper + + if (l > k2) { + l = k2; +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineLayer.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineLayer.java +index 944a8c295ff9df0d96800ddc4f6763598cf61d0d..64dad8ed7c16011d9cb3e9d22ac6f892c638e3b2 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineLayer.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineLayer.java +@@ -10,6 +10,7 @@ import net.minecraft.world.level.EnumSkyBlock; + import net.minecraft.world.level.IBlockAccess; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.IBlockData; ++import net.minecraft.world.level.chunk.IChunkAccess; + import net.minecraft.world.level.chunk.ILightAccess; + import net.minecraft.world.level.chunk.NibbleArray; + import net.minecraft.world.phys.shapes.VoxelShape; +@@ -23,10 +24,37 @@ public abstract class LightEngineLayer, S e + protected final EnumSkyBlock b; + protected final S c; + private boolean f; +- protected final BlockPosition.MutableBlockPosition d = new BlockPosition.MutableBlockPosition(); ++ protected final BlockPosition.MutableBlockPosition d = new BlockPosition.MutableBlockPosition(); protected final BlockPosition.MutableBlockPosition pos = d; // Paper + private final long[] g = new long[2]; +- private final IBlockAccess[] h = new IBlockAccess[2]; ++ private final IChunkAccess[] h = new IChunkAccess[2]; // Paper + ++ // Paper start - see fully commented out method below (look for Bedrock) ++ // optimized method with less branching for when scenarios arent needed. ++ // avoid using mutable version if can ++ protected final IBlockData getBlockOptimized(int x, int y, int z, MutableInt mutableint) { ++ IChunkAccess iblockaccess = this.a(x >> 4, z >> 4); ++ ++ if (iblockaccess == null) { ++ mutableint.setValue(16); ++ return Blocks.BEDROCK.getBlockData(); ++ } else { ++ this.pos.setValues(x, y, z); ++ IBlockData iblockdata = iblockaccess.getType(x, y, z); ++ mutableint.setValue(iblockdata.b(this.a.getWorld(), this.pos)); ++ return iblockdata.l() && iblockdata.e() ? iblockdata : Blocks.AIR.getBlockData(); ++ } ++ } ++ protected final IBlockData getBlockOptimized(int x, int y, int z) { ++ IChunkAccess iblockaccess = this.a(x >> 4, z >> 4); ++ ++ if (iblockaccess == null) { ++ return Blocks.BEDROCK.getBlockData(); ++ } else { ++ IBlockData iblockdata = iblockaccess.getType(x, y, z); ++ return iblockdata.l() && iblockdata.e() ? iblockdata : Blocks.AIR.getBlockData(); ++ } ++ } ++ // Paper end + public LightEngineLayer(ILightAccess ilightaccess, EnumSkyBlock enumskyblock, S s0) { + super(16, 256, 8192); + this.a = ilightaccess; +@@ -45,7 +73,7 @@ public abstract class LightEngineLayer, S e + } + + @Nullable +- private IBlockAccess a(int i, int j) { ++ private IChunkAccess a(int i, int j) { // Paper + long k = ChunkCoordIntPair.pair(i, j); + + for (int l = 0; l < 2; ++l) { +@@ -54,7 +82,7 @@ public abstract class LightEngineLayer, S e + } + } + +- IBlockAccess iblockaccess = this.a.c(i, j); ++ IChunkAccess iblockaccess = (IChunkAccess) this.a.c(i, j); // Paper + + for (int i1 = 1; i1 > 0; --i1) { + this.g[i1] = this.g[i1 - 1]; +@@ -71,37 +99,39 @@ public abstract class LightEngineLayer, S e + Arrays.fill(this.h, (Object) null); + } + +- protected IBlockData a(long i, @Nullable MutableInt mutableint) { +- if (i == Long.MAX_VALUE) { +- if (mutableint != null) { +- mutableint.setValue(0); +- } +- +- return Blocks.AIR.getBlockData(); +- } else { +- int j = SectionPosition.a(BlockPosition.b(i)); +- int k = SectionPosition.a(BlockPosition.d(i)); +- IBlockAccess iblockaccess = this.a(j, k); +- +- if (iblockaccess == null) { +- if (mutableint != null) { +- mutableint.setValue(16); +- } +- +- return Blocks.BEDROCK.getBlockData(); +- } else { +- this.d.g(i); +- IBlockData iblockdata = iblockaccess.getType(this.d); +- boolean flag = iblockdata.l() && iblockdata.e(); +- +- if (mutableint != null) { +- mutableint.setValue(iblockdata.b(this.a.getWorld(), (BlockPosition) this.d)); +- } +- +- return flag ? iblockdata : Blocks.AIR.getBlockData(); +- } +- } +- } ++ // Paper start - comment out, see getBlockOptimized ++// protected IBlockData a(long i, @Nullable MutableInt mutableint) { ++// if (i == Long.MAX_VALUE) { ++// if (mutableint != null) { ++// mutableint.setValue(0); ++// } ++// ++// return Blocks.AIR.getBlockData(); ++// } else { ++// int j = SectionPosition.a(BlockPosition.b(i)); ++// int k = SectionPosition.a(BlockPosition.d(i)); ++// IBlockAccess iblockaccess = this.a(j, k); ++// ++// if (iblockaccess == null) { ++// if (mutableint != null) { ++// mutableint.setValue(16); ++// } ++// ++// return Blocks.BEDROCK.getBlockData(); ++// } else { ++// this.d.g(i); ++// IBlockData iblockdata = iblockaccess.getType(this.d); ++// boolean flag = iblockdata.l() && iblockdata.e(); ++// ++// if (mutableint != null) { ++// mutableint.setValue(iblockdata.b(this.a.getWorld(), (BlockPosition) this.d)); ++// } ++// ++// return flag ? iblockdata : Blocks.AIR.getBlockData(); ++// } ++// } ++// } ++ // Paper end + + protected VoxelShape a(IBlockData iblockdata, long i, EnumDirection enumdirection) { + return iblockdata.l() ? iblockdata.a(this.a.getWorld(), this.d.g(i), enumdirection) : VoxelShapes.a(); +@@ -136,8 +166,9 @@ public abstract class LightEngineLayer, S e + return i == Long.MAX_VALUE ? 0 : 15 - this.c.i(i); + } + ++ protected int getNibbleLightInverse(NibbleArray nibblearray, int x, int y, int z) { return 15 - nibblearray.a(x & 15, y & 15, z & 15); } // Paper - x/y/z version of below + protected int a(NibbleArray nibblearray, long i) { +- return 15 - nibblearray.a(SectionPosition.b(BlockPosition.b(i)), SectionPosition.b(BlockPosition.c(i)), SectionPosition.b(BlockPosition.d(i))); ++ return 15 - nibblearray.a((int) (i >> 38) & 15, (int) ((i << 52) >> 52) & 15, (int) ((i << 26) >> 38) & 15); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineSky.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineSky.java +index 37fa5faea6e2972e3eb8a3cbd1913ef38dc9456f..9cd2dfbfa216fdc58297fd25066d31bb92e13ec2 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineSky.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineSky.java +@@ -4,6 +4,7 @@ import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; + import net.minecraft.core.SectionPosition; + import net.minecraft.world.level.EnumSkyBlock; ++import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.level.chunk.ILightAccess; + import net.minecraft.world.level.chunk.NibbleArray; +@@ -38,21 +39,25 @@ public final class LightEngineSky extends LightEngineLayer= 15) { ++ // Paper start - use x/y/z and optimized block lookup ++ int jx = (int) (j >> 38); ++ int jy = (int) ((j << 52) >> 52); ++ int jz = (int) ((j << 26) >> 38); ++ IBlockData iblockdata = this.getBlockOptimized(jx, jy, jz, mutableint); ++ int blockedLight = mutableint.getValue(); ++ if (blockedLight >= 15) { ++ // Paper end + return 15; + } else { +- int l = BlockPosition.b(i); +- int i1 = BlockPosition.c(i); +- int j1 = BlockPosition.d(i); +- int k1 = BlockPosition.b(j); +- int l1 = BlockPosition.c(j); +- int i2 = BlockPosition.d(j); +- boolean flag = l == k1 && j1 == i2; +- int j2 = Integer.signum(k1 - l); +- int k2 = Integer.signum(l1 - i1); +- int l2 = Integer.signum(i2 - j1); ++ // Paper start - inline math ++ int ix = (int) (i >> 38); ++ int iy = (int) ((i << 52) >> 52); ++ int iz = (int) ((i << 26) >> 38); ++ boolean flag = ix == jx && iz == jz; ++ int j2 = Integer.signum(jx - ix); ++ int k2 = Integer.signum(jy - iy); ++ int l2 = Integer.signum(jz - iz); ++ // Paper end + EnumDirection enumdirection; + + if (i == Long.MAX_VALUE) { +@@ -61,7 +66,7 @@ public final class LightEngineSky extends LightEngineLayer l1; ++ boolean flag1 = i == Long.MAX_VALUE || flag && iy > jy; // Paper rename vars to iy > jy + +- return flag1 && k == 0 && mutableint.getValue() == 0 ? 0 : k + Math.max(1, mutableint.getValue()); ++ return flag1 && k == 0 && blockedLight == 0 ? 0 : k + Math.max(1, blockedLight); // Paper + } + } + } +@@ -101,10 +106,14 @@ public final class LightEngineSky extends LightEngineLayer> 38); ++ int baseY = (int) ((i << 52) >> 52); ++ int baseZ = (int) ((i << 26) >> 38); ++ long k = SectionPosition.blockPosAsSectionLong(baseX, baseY, baseZ); ++ int i1 = baseY & 15; ++ int j1 = baseY >> 4; ++ // Paper end + int k1; + + if (i1 != 0) { +@@ -119,15 +128,16 @@ public final class LightEngineSky extends LightEngineLayer> 38); ++ int baseY = (int) ((i << 52) >> 52); ++ int baseZ = (int) ((i << 26) >> 38); ++ long j1 = SectionPosition.blockPosAsSectionLong(baseX, baseY, baseZ); ++ NibbleArray nibblearray = this.c.updating.getUpdatingOptimized(j1); ++ // Paper end + EnumDirection[] aenumdirection = LightEngineSky.e; + int k1 = aenumdirection.length; + + for (int l1 = 0; l1 < k1; ++l1) { + EnumDirection enumdirection = aenumdirection[l1]; +- long i2 = BlockPosition.a(i, enumdirection); +- long j2 = SectionPosition.e(i2); ++ // Paper start ++ int newX = baseX + enumdirection.getAdjacentX(); ++ int newY = baseY + enumdirection.getAdjacentY(); ++ int newZ = baseZ + enumdirection.getAdjacentZ(); ++ long i2 = BlockPosition.asLong(newX, newY, newZ); ++ long j2 = SectionPosition.blockPosAsSectionLong(newX, newY, newZ); ++ // Paper end + NibbleArray nibblearray1; + + if (j1 == j2) { + nibblearray1 = nibblearray; + } else { +- nibblearray1 = ((LightEngineStorageSky) this.c).a(j2, true); ++ nibblearray1 = ((LightEngineStorageSky) this.c).updating.getUpdatingOptimized(j2); // Paper + } + + if (nibblearray1 != null) { + if (i2 != j) { +- int k2 = this.b(i2, i, this.a(nibblearray1, i2)); ++ int k2 = this.b(i2, i, this.getNibbleLightInverse(nibblearray1, newX, newY, newZ)); // Paper + + if (l > k2) { + l = k2; +@@ -215,7 +235,7 @@ public final class LightEngineSky extends LightEngineLayer> e + protected final LongSet c = new LongOpenHashSet(); + protected final LongSet d = new LongOpenHashSet(); + protected volatile M e_visible; protected final Object visibleUpdateLock = new Object(); // Paper - diff on change, should be "visible" - force compile fail on usage change +- protected final M f; // Paper - diff on change, should be "updating" ++ protected final M f; protected final M updating; // Paper - diff on change, should be "updating" + protected final LongSet g = new LongOpenHashSet(); +- protected final LongSet h = new LongOpenHashSet(); ++ protected final LongSet h = new LongOpenHashSet(); LongSet dirty = h; // Paper - OBFHELPER + protected final Long2ObjectMap i = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap()); + private final LongSet n = new LongOpenHashSet(); + private final LongSet o = new LongOpenHashSet(); +@@ -37,33 +37,33 @@ public abstract class LightEngineStorage> e + protected volatile boolean j; + + protected LightEngineStorage(EnumSkyBlock enumskyblock, ILightAccess ilightaccess, M m0) { +- super(3, 16, 256); ++ super(3, 256, 256); // Paper - bump expected size of level sets to improve collisions and reduce rehashing (seen a lot of it) + this.l = enumskyblock; + this.m = ilightaccess; +- this.f = m0; ++ this.f = m0; updating = m0; // Paper + this.e_visible = m0.b(); // Paper - avoid copying light data + this.e_visible.d(); // Paper - avoid copying light data + } + +- protected boolean g(long i) { +- return this.a(i, true) != null; ++ protected final boolean g(long i) { // Paper - final to help inlining ++ return this.updating.getUpdatingOptimized(i) != null; // Paper - inline to avoid branching + } + + @Nullable + protected NibbleArray a(long i, boolean flag) { + // Paper start - avoid copying light data + if (flag) { +- return this.a(this.f, i); ++ return this.updating.getUpdatingOptimized(i); + } else { + synchronized (this.visibleUpdateLock) { +- return this.a(this.e_visible, i); ++ return this.e_visible.lookup.apply(i); + } + } + // Paper end - avoid copying light data + } + + @Nullable +- protected NibbleArray a(M m0, long i) { ++ protected final NibbleArray a(M m0, long i) { // Paper + return m0.c(i); + } + +@@ -77,27 +77,57 @@ public abstract class LightEngineStorage> e + protected abstract int d(long i); + + protected int i(long i) { +- long j = SectionPosition.e(i); +- NibbleArray nibblearray = this.a(j, true); ++ // Paper start - reuse and inline math, use Optimized Updating path ++ final int x = (int) (i >> 38); ++ final int y = (int) ((i << 52) >> 52); ++ final int z = (int) ((i << 26) >> 38); ++ long j = SectionPosition.blockPosAsSectionLong(x, y, z); ++ NibbleArray nibblearray = this.updating.getUpdatingOptimized(j); ++ // BUG: Sometimes returns null and crashes, try to recover, but to prevent crash just return no light. ++ if (nibblearray == null) { ++ nibblearray = this.e_visible.lookup.apply(j); ++ } ++ if (nibblearray == null) { ++ System.err.println("Null nibble, preventing crash " + BlockPosition.fromLong(i)); ++ return 0; ++ } + +- return nibblearray.a(SectionPosition.b(BlockPosition.b(i)), SectionPosition.b(BlockPosition.c(i)), SectionPosition.b(BlockPosition.d(i))); ++ return nibblearray.a(x & 15, y & 15, z & 15); // Paper - inline operations ++ // Paper end + } + + protected void b(long i, int j) { +- long k = SectionPosition.e(i); ++ // Paper start - cache part of the math done in loop below ++ int x = (int) (i >> 38); ++ int y = (int) ((i << 52) >> 52); ++ int z = (int) ((i << 26) >> 38); ++ long k = SectionPosition.blockPosAsSectionLong(x, y, z); ++ // Paper end + + if (this.g.add(k)) { + this.f.a(k); + } + + NibbleArray nibblearray = this.a(k, true); +- +- nibblearray.a(SectionPosition.b(BlockPosition.b(i)), SectionPosition.b(BlockPosition.c(i)), SectionPosition.b(BlockPosition.d(i)), j); +- +- for (int l = -1; l <= 1; ++l) { +- for (int i1 = -1; i1 <= 1; ++i1) { +- for (int j1 = -1; j1 <= 1; ++j1) { +- this.h.add(SectionPosition.e(BlockPosition.a(i, i1, j1, l))); ++ nibblearray.a(x & 15, y & 15, z & 15, j); // Paper - use already calculated x/y/z ++ ++ // Paper start - credit to JellySquid for a major optimization here: ++ /* ++ * An extremely important optimization is made here in regards to adding items to the pending notification set. The ++ * original implementation attempts to add the coordinate of every chunk which contains a neighboring block position ++ * even though a huge number of loop iterations will simply map to block positions within the same updating chunk. ++ * ++ * Our implementation here avoids this by pre-calculating the min/max chunk coordinates so we can iterate over only ++ * the relevant chunk positions once. This reduces what would always be 27 iterations to just 1-8 iterations. ++ * ++ * @reason Use faster implementation ++ * @author JellySquid ++ */ ++ for (int z2 = (z - 1) >> 4; z2 <= (z + 1) >> 4; ++z2) { ++ for (int x2 = (x - 1) >> 4; x2 <= (x + 1) >> 4; ++x2) { ++ for (int y2 = (y - 1) >> 4; y2 <= (y + 1) >> 4; ++y2) { ++ this.dirty.add(SectionPosition.asLong(x2, y2, z2)); ++ // Paper end + } + } + } +@@ -129,17 +159,23 @@ public abstract class LightEngineStorage> e + } + + if (k >= 2 && j != 2) { +- if (this.p.contains(i)) { +- this.p.remove(i); +- } else { ++ if (!this.p.remove(i)) { // Paper - remove useless contains - credit to JellySquid ++ //this.p.remove(i); // Paper ++ //} else { // Paper + this.f.a(i, this.j(i)); + this.g.add(i); + this.k(i); + +- for (int l = -1; l <= 1; ++l) { +- for (int i1 = -1; i1 <= 1; ++i1) { +- for (int j1 = -1; j1 <= 1; ++j1) { +- this.h.add(SectionPosition.e(BlockPosition.a(i, i1, j1, l))); ++ // Paper start - reuse x/y/z and only notify valid chunks - Credit to JellySquid (See above method for notes) ++ int x = (int) (i >> 38); ++ int y = (int) ((i << 52) >> 52); ++ int z = (int) ((i << 26) >> 38); ++ ++ for (int z2 = (z - 1) >> 4; z2 <= (z + 1) >> 4; ++z2) { ++ for (int x2 = (x - 1) >> 4; x2 <= (x + 1) >> 4; ++x2) { ++ for (int y2 = (y - 1) >> 4; y2 <= (y + 1) >> 4; ++y2) { ++ this.dirty.add(SectionPosition.asLong(x2, y2, z2)); ++ // Paper end + } + } + } +@@ -165,9 +201,9 @@ public abstract class LightEngineStorage> e + return SectionPosition.e(j) == i; + }); + } else { +- int j = SectionPosition.c(SectionPosition.b(i)); +- int k = SectionPosition.c(SectionPosition.c(i)); +- int l = SectionPosition.c(SectionPosition.d(i)); ++ int j = (int) (i >> 42) << 4; // Paper - inline ++ int k = (int) (i << 44 >> 44) << 4; // Paper - inline ++ int l = (int) (i << 22 >> 42) << 4; // Paper - inline + + for (int i1 = 0; i1 < 16; ++i1) { + for (int j1 = 0; j1 < 16; ++j1) { +@@ -194,7 +230,7 @@ public abstract class LightEngineStorage> e + NibbleArray nibblearray; + + while (longiterator.hasNext()) { +- i = (Long) longiterator.next(); ++ i = longiterator.nextLong(); // Paper + this.a(lightenginelayer, i); + NibbleArray nibblearray1 = (NibbleArray) this.i.remove(i); + +@@ -212,7 +248,7 @@ public abstract class LightEngineStorage> e + longiterator = this.p.iterator(); + + while (longiterator.hasNext()) { +- i = (Long) longiterator.next(); ++ i = longiterator.nextLong(); // Paper + this.l(i); + } + +@@ -223,12 +259,13 @@ public abstract class LightEngineStorage> e + Entry entry; + long j; + ++ NibbleArray test = null; // Paper + while (objectiterator.hasNext()) { + entry = (Entry) objectiterator.next(); + j = entry.getLongKey(); +- if (this.g(j)) { ++ if ((test = this.updating.getUpdatingOptimized(j)) != null) { // Paper - dont look up nibble twice + nibblearray = (NibbleArray) entry.getValue(); +- if (this.f.c(j) != nibblearray) { ++ if (test != nibblearray) { // Paper + this.a(lightenginelayer, j); + this.f.a(j, nibblearray); + this.g.add(j); +@@ -241,14 +278,14 @@ public abstract class LightEngineStorage> e + longiterator = this.i.keySet().iterator(); + + while (longiterator.hasNext()) { +- i = (Long) longiterator.next(); ++ i = longiterator.nextLong(); // Paper + this.b(lightenginelayer, i); + } + } else { + longiterator = this.n.iterator(); + + while (longiterator.hasNext()) { +- i = (Long) longiterator.next(); ++ i = longiterator.nextLong(); // Paper + this.b(lightenginelayer, i); + } + } +@@ -269,15 +306,20 @@ public abstract class LightEngineStorage> e + + private void b(LightEngineLayer lightenginelayer, long i) { + if (this.g(i)) { +- int j = SectionPosition.c(SectionPosition.b(i)); +- int k = SectionPosition.c(SectionPosition.c(i)); +- int l = SectionPosition.c(SectionPosition.d(i)); ++ // Paper start ++ int secX = (int) (i >> 42); ++ int secY = (int) (i << 44 >> 44); ++ int secZ = (int) (i << 22 >> 42); ++ int j = secX << 4; // baseX ++ int k = secY << 4; // baseY ++ int l = secZ << 4; // baseZ ++ // Paper end + EnumDirection[] aenumdirection = LightEngineStorage.k; + int i1 = aenumdirection.length; + + for (int j1 = 0; j1 < i1; ++j1) { + EnumDirection enumdirection = aenumdirection[j1]; +- long k1 = SectionPosition.a(i, enumdirection); ++ long k1 = SectionPosition.getAdjacentFromSectionPos(secX, secY, secZ, enumdirection); // Paper - avoid extra unpacking + + if (!this.i.containsKey(k1) && this.g(k1)) { + for (int l1 = 0; l1 < 16; ++l1) { +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java +index da78d4c4b5f8af4648ac82d63c21f6a2a5b73ecb..2ce5cf2e5b6e1dae463439fbfde519fa54677714 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageArray.java +@@ -7,13 +7,18 @@ import net.minecraft.world.level.chunk.NibbleArray; + + public abstract class LightEngineStorageArray> { + +- private final long[] b = new long[2]; +- private final NibbleArray[] c = new NibbleArray[2]; ++ // private final long[] b = new long[2]; // Paper - unused ++ private final NibbleArray[] c = new NibbleArray[]{NibbleArray.EMPTY_NIBBLE_ARRAY, NibbleArray.EMPTY_NIBBLE_ARRAY}; private final NibbleArray[] cache = c; // Paper - OBFHELPER + private boolean d; + protected final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data; // Paper - avoid copying light data + protected final boolean isVisible; // Paper - avoid copying light data +- java.util.function.Function lookup; // Paper - faster branchless lookup + ++ // Paper start - faster lookups with less branching, use interface to avoid boxing instead of Function ++ public final NibbleArrayAccess lookup; ++ public interface NibbleArrayAccess { ++ NibbleArray apply(long id); ++ } ++ // Paper end + // Paper start - avoid copying light data + protected LightEngineStorageArray(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data, boolean isVisible) { + if (isVisible) { +@@ -21,12 +26,14 @@ public abstract class LightEngineStorageArray 0; --k) { +- this.b[k] = this.b[k - 1]; +- this.c[k] = this.c[k - 1]; +- } +- +- this.b[0] = i; +- this.c[0] = nibblearray; +- } +- ++ cache[1] = cache[0]; ++ cache[0] = nibblearray; + return nibblearray; + } + } ++ // Paper end ++ ++ @Nullable ++ public final NibbleArray c(final long i) { // Paper - final ++ // Paper start - optimize visible case or missed updating cases ++ if (this.d) { ++ // short circuit to optimized ++ return getUpdatingOptimized(i); ++ } ++ ++ return this.lookup.apply(i); ++ // Paper end ++ } + + @Nullable + public NibbleArray d(long i) { +@@ -82,13 +91,14 @@ public abstract class LightEngineStorageArray> 38); ++ int baseY = (int) ((i << 52) >> 52); ++ int baseZ = (int) ((i << 26) >> 38); ++ long j = (((long) (baseX >> 4) & 4194303L) << 42) | (((long) (baseY >> 4) & 1048575L)) | (((long) (baseZ >> 4) & 4194303L) << 20); ++ NibbleArray nibblearray = this.e_visible.lookup.apply(j); ++ return nibblearray == null ? 0 : nibblearray.a(baseX & 15, baseY & 15, baseZ & 15); ++ // Paper end + } + + public static final class a extends LightEngineStorageArray { +diff --git a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java +index 488403a6765598317faedc2d600ae82238e99e39..6d31b19c851081a37e6fcefdcdfcb7018fce6b26 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LightEngineStorageSky.java +@@ -28,7 +28,12 @@ public class LightEngineStorageSky extends LightEngineStorage> 38); ++ int baseY = (int) ((i << 52) >> 52); ++ int baseZ = (int) ((i << 26) >> 38); ++ long j = SectionPosition.blockPosAsSectionLong(baseX, baseY, baseZ); ++ // Paper end + int k = SectionPosition.c(j); + synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data + LightEngineStorageSky.a lightenginestoragesky_a = (LightEngineStorageSky.a) this.e_visible; // Paper - avoid copying light data - must be after lock acquire +@@ -49,7 +54,7 @@ public class LightEngineStorageSky extends LightEngineStorage> 52) & 15, (int) baseZ & 15); // Paper - y changed above + } else { + return 15; + } +@@ -168,7 +173,7 @@ public class LightEngineStorageSky extends LightEngineStorage> 42) << 4; // Paper ++ int baseY = (int) (i << 44 >> 44) << 4; // Paper ++ int baseZ = (int) (i << 22 >> 42) << 4; // Paper + j = this.c(i); + if (j != 2 && !this.n.contains(i) && this.l.add(i)) { + int l; +@@ -203,10 +211,10 @@ public class LightEngineStorageSky extends LightEngineStorage> 42) << 4; // Paper ++ int baseY = (int) (i << 44 >> 44) << 4; // Paper ++ int baseZ = (int) (i << 22 >> 42) << 4; // Paper + if (this.l.remove(i) && this.g(i)) { + for (j = 0; j < 16; ++j) { + for (k = 0; k < 16; ++k) { +- long l3 = BlockPosition.a(SectionPosition.c(SectionPosition.b(i)) + j, SectionPosition.c(SectionPosition.c(i)) + 16 - 1, SectionPosition.c(SectionPosition.d(i)) + k); ++ long l3 = BlockPosition.a(baseX + j, baseY + 16 - 1, baseZ + k); // Paper + + lightenginelayer.a(Long.MAX_VALUE, l3, 15, false); + } diff --git a/patches/server-unmapped/0001/0495-Delay-Chunk-Unloads-based-on-Player-Movement.patch b/patches/server-unmapped/0001/0495-Delay-Chunk-Unloads-based-on-Player-Movement.patch new file mode 100644 index 0000000000..d2c3fa9257 --- /dev/null +++ b/patches/server-unmapped/0001/0495-Delay-Chunk-Unloads-based-on-Player-Movement.patch @@ -0,0 +1,107 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 18 Jun 2016 23:22:12 -0400 +Subject: [PATCH] Delay Chunk Unloads based on Player Movement + +When players are moving in the world, doing things such as building or exploring, +they will commonly go back and forth in a small area. This causes a ton of chunk load +and unload activity on the edge chunks of their view distance. + +A simple back and forth movement in 6 blocks could spam a chunk to thrash a +loading and unload cycle over and over again. + +This is very wasteful. This system introduces a delay of inactivity on a chunk +before it actually unloads, which will be handled by the ticket expiry process. + +This allows servers with smaller worlds who do less long distance exploring to stop +wasting cpu cycles on saving/unloading/reloading chunks repeatedly. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 6463d3e4837d032a35654a035f42b8a805e0e286..1655bca0502e7b871de4addaa163536d86547a02 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -637,4 +637,13 @@ public class PaperWorldConfig { + private void viewDistance() { + this.noTickViewDistance = this.getInt("viewdistances.no-tick-view-distance", -1); + } ++ ++ public long delayChunkUnloadsBy; ++ private void delayChunkUnloadsBy() { ++ delayChunkUnloadsBy = PaperConfig.getSeconds(getString("delay-chunk-unloads-by", "10s")); ++ if (delayChunkUnloadsBy > 0) { ++ log("Delaying chunk unloads by " + delayChunkUnloadsBy + " seconds"); ++ delayChunkUnloadsBy *= 20; ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +index a3c44fdfca8290313b9b1117b984533183b583ad..3644e8b24b082e17752ef52934625416130aaa08 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java +@@ -185,6 +185,27 @@ public abstract class ChunkMapDistance { + boolean removed = false; // CraftBukkit + if (arraysetsorted.remove(ticket)) { + removed = true; // CraftBukkit ++ // Paper start - delay chunk unloads for player tickets ++ long delayChunkUnloadsBy = chunkMap.world.paperConfig.delayChunkUnloadsBy; ++ if (ticket.getTicketType() == TicketType.PLAYER && delayChunkUnloadsBy > 0) { ++ boolean hasPlayer = false; ++ for (Ticket ticket1 : arraysetsorted) { ++ if (ticket1.getTicketType() == TicketType.PLAYER) { ++ hasPlayer = true; ++ break; ++ } ++ } ++ PlayerChunk playerChunk = chunkMap.getUpdatingChunk(i); ++ if (!hasPlayer && playerChunk != null && playerChunk.isFullChunkReady()) { ++ Ticket delayUnload = new Ticket(TicketType.DELAY_UNLOAD, 33, i); ++ delayUnload.delayUnloadBy = delayChunkUnloadsBy; ++ delayUnload.setCurrentTick(this.currentTick); ++ arraysetsorted.remove(delayUnload); ++ // refresh ticket ++ arraysetsorted.add(delayUnload); ++ } ++ } ++ // Paper end + } + + if (arraysetsorted.isEmpty()) { +diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java +index 90cbd12611b7b078f35f08f910453bcc02f6665b..6e5ae954c6eb40590bf8c83f592c22088d489be8 100644 +--- a/src/main/java/net/minecraft/server/level/Ticket.java ++++ b/src/main/java/net/minecraft/server/level/Ticket.java +@@ -9,11 +9,13 @@ public final class Ticket implements Comparable> { + public final T identifier; public final T getObjectReason() { return this.identifier; } // Paper - OBFHELPER + private long d; public final long getCreationTick() { return this.d; } // Paper - OBFHELPER + public int priority = 0; // Paper ++ public long delayUnloadBy; // Paper + + protected Ticket(TicketType tickettype, int i, T t0) { + this.a = tickettype; + this.b = i; + this.identifier = t0; ++ this.delayUnloadBy = tickettype.loadPeriod; // Paper + } + + public int compareTo(Ticket ticket) { +@@ -63,7 +65,7 @@ public final class Ticket implements Comparable> { + } + + protected boolean b(long i) { +- long j = this.a.b(); ++ long j = delayUnloadBy; // Paper + + return j != 0L && i - this.d > j; + } +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index f5d18834e0e2ee0e3bcf55810456766d2f134450..3c804c7b20a14ea6e510810e2be10c1cc89ff5c1 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -30,6 +30,7 @@ public class TicketType { + public static final TicketType ASYNC_LOAD = a("async_load", Long::compareTo); // Paper + public static final TicketType PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper + public static final TicketType URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper ++ public static final TicketType DELAY_UNLOAD = a("delay_unload", Long::compareTo, 300); // Paper + + public static TicketType a(String s, Comparator comparator) { + return new TicketType<>(s, comparator, 0L); diff --git a/patches/server-unmapped/0001/0496-Add-Plugin-Tickets-to-API-Chunk-Methods.patch b/patches/server-unmapped/0001/0496-Add-Plugin-Tickets-to-API-Chunk-Methods.patch new file mode 100644 index 0000000000..6b2a11e0b9 --- /dev/null +++ b/patches/server-unmapped/0001/0496-Add-Plugin-Tickets-to-API-Chunk-Methods.patch @@ -0,0 +1,129 @@ +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 32e68e403950be62bd0330b268738225c2e70edd..d0c951878600a4227e558bc68d68098c59fb7b2b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -346,7 +346,7 @@ public final class CraftServer implements Server { + ambientSpawn = configuration.getInt("spawn-limits.ambient"); + console.autosavePeriod = configuration.getInt("ticks-per.autosave"); + warningState = WarningState.value(configuration.getString("settings.deprecated-verbose")); +- TicketType.PLUGIN.loadPeriod = configuration.getInt("chunk-gc.period-in-ticks"); ++ TicketType.PLUGIN.loadPeriod = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second + minimumAPI = configuration.getString("settings.minimum-api"); + loadIcon(); + } +@@ -837,7 +837,7 @@ public final class CraftServer implements Server { + waterAmbientSpawn = configuration.getInt("spawn-limits.water-ambient"); + ambientSpawn = configuration.getInt("spawn-limits.ambient"); + warningState = WarningState.value(configuration.getString("settings.deprecated-verbose")); +- TicketType.PLUGIN.loadPeriod = configuration.getInt("chunk-gc.period-in-ticks"); ++ TicketType.PLUGIN.loadPeriod = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second + minimumAPI = configuration.getString("settings.minimum-api"); + printSaveWarning = false; + console.autosavePeriod = 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 2511c44cc62b7cdce17741c786a58f73f4252955..03761fd7367e184c808f4ef55bdef0838f7427b0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -30,6 +30,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutCustomSoundEffect; + import net.minecraft.network.protocol.game.PacketPlayOutUpdateTime; + import net.minecraft.network.protocol.game.PacketPlayOutWorldEvent; + import net.minecraft.resources.MinecraftKey; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.ChunkMapDistance; + import net.minecraft.server.level.PlayerChunk; + import net.minecraft.server.level.PlayerChunkMap; +@@ -407,8 +408,21 @@ public class CraftWorld implements World { + + @Override + public Chunk getChunkAt(int x, int z) { +- return this.world.getChunkProvider().getChunkAt(x, z, true).bukkitChunk; ++ // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it ++ net.minecraft.world.level.chunk.Chunk chunk = world.getChunkProvider().getChunkAtIfLoadedImmediately(x, z); ++ if (chunk == null) { ++ addTicket(x, z); ++ chunk = this.world.getChunkProvider().getChunkAt(x, z, true); ++ } ++ return chunk.bukkitChunk; ++ // Paper end ++ } ++ ++ // Paper start ++ private void addTicket(int x, int z) { ++ MCUtil.MAIN_EXECUTOR.execute(() -> world.getChunkProvider().addTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 0, Unit.INSTANCE)); // Paper + } ++ // Paper end + + @Override + public Chunk getChunkAt(Block block) { +@@ -483,7 +497,7 @@ public class CraftWorld implements World { + public boolean unloadChunkRequest(int x, int z) { + org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot + if (isChunkLoaded(x, z)) { +- world.getChunkProvider().removeTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 1, Unit.INSTANCE); ++ world.getChunkProvider().removeTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 0, Unit.INSTANCE); // Paper + } + + return true; +@@ -560,9 +574,12 @@ public class CraftWorld implements World { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot + // Paper start - Optimize this method + ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(x, z); ++ IChunkAccess immediate = world.getChunkProvider().getChunkAtIfLoadedImmediately(x, z); // Paper ++ if (immediate != null) return true; // Paper + + if (!generate) { +- IChunkAccess immediate = world.getChunkProvider().getChunkAtImmediately(x, z); ++ ++ //IChunkAccess immediate = world.getChunkProvider().getChunkAtImmediately(x, z); // Paper + if (immediate == null) { + immediate = world.getChunkProvider().playerChunkMap.getUnloadingChunk(x, z); + } +@@ -570,7 +587,7 @@ public class CraftWorld implements World { + if (!(immediate instanceof ProtoChunkExtension) && !(immediate instanceof net.minecraft.world.level.chunk.Chunk)) { + return false; // not full status + } +- world.getChunkProvider().addTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); ++ world.getChunkProvider().addTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper + world.getChunkAt(x, z); // make sure we're at ticket level 32 or lower + return true; + } +@@ -597,7 +614,7 @@ public class CraftWorld implements World { + // we do this so we do not re-read the chunk data on disk + } + +- world.getChunkProvider().addTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); ++ world.getChunkProvider().addTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper + world.getChunkProvider().getChunkAt(x, z, ChunkStatus.FULL, true); + return true; + // Paper end +@@ -2547,6 +2564,7 @@ public class CraftWorld implements World { + } + return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { + net.minecraft.world.level.chunk.Chunk chunk = (net.minecraft.world.level.chunk.Chunk) either.left().orElse(null); ++ if (chunk != null) addTicket(x, z); // Paper + return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); + }, net.minecraft.server.MinecraftServer.getServer()); + } diff --git a/patches/server-unmapped/0001/0497-Fix-missing-chunks-due-to-integer-overflow.patch b/patches/server-unmapped/0001/0497-Fix-missing-chunks-due-to-integer-overflow.patch new file mode 100644 index 0000000000..15f03e8c36 --- /dev/null +++ b/patches/server-unmapped/0001/0497-Fix-missing-chunks-due-to-integer-overflow.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: David Slovikosky +Date: Tue, 9 Jun 2020 00:10:03 -0700 +Subject: [PATCH] Fix missing chunks due to integer overflow + +This patch fixes a bug in the WorldChunkManagerTheEnd class where the distance +from 0,0 squared overflows the maximum size of an integer. The overflow leads +to hard chunk borders around 370,000 blocks from 0,0. After this cutoff there +is a few hundred thousand block gap before end land resuming to generate at +530,000 blocks from spawn. This is due to the integer flipping back and forth. + +The fix for the issue is quite simple, casting chunk coordinates to longs +allows the distance calculation to avoid overflow and work as intended. + +diff --git a/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerTheEnd.java b/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerTheEnd.java +index 1d46e2c4e06cfe32eac06223e1966ce39c41685e..1077972d694d604c3ec97d333d34a9ab81819c33 100644 +--- a/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerTheEnd.java ++++ b/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerTheEnd.java +@@ -75,7 +75,9 @@ public class WorldChunkManagerTheEnd extends WorldChunkManager { + int l = j / 2; + int i1 = i % 2; + int j1 = j % 2; +- float f = 100.0F - MathHelper.c((float) (i * i + j * j)) * 8.0F; ++ // Paper start - cast ints to long to avoid integer overflow ++ float f = 100.0F - MathHelper.sqrt((long) i * (long) i + (long) j * (long) j) * 8.0F; ++ // Paper end + + f = MathHelper.a(f, -100.0F, 80.0F); + diff --git a/patches/server-unmapped/0001/0498-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch b/patches/server-unmapped/0001/0498-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch new file mode 100644 index 0000000000..423af0ebf9 --- /dev/null +++ b/patches/server-unmapped/0001/0498-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ossi +Date: Fri, 12 Jun 2020 01:38:06 +0300 +Subject: [PATCH] Fix CraftScheduler#runTaskTimerAsynchronously(Plugin, + Consumer, long, long) scheduling a non-repeating task instead of + a repeating one. + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index ca90237a53c9a026919d28adaedf483ca3c7c2a8..13e461ffb2ee2e7d0440c0f60809ea99629b843c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -184,7 +184,7 @@ public class CraftScheduler implements BukkitScheduler { + + @Override + public void runTaskTimerAsynchronously(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException { +- runTaskTimerAsynchronously(plugin, (Object) task, delay, CraftTask.NO_REPEATING); ++ runTaskTimerAsynchronously(plugin, (Object) task, delay, period); + } + + @Override diff --git a/patches/server-unmapped/0001/0499-Fix-piston-physics-inconsistency-MC-188840.patch b/patches/server-unmapped/0001/0499-Fix-piston-physics-inconsistency-MC-188840.patch new file mode 100644 index 0000000000..86e65e4b6f --- /dev/null +++ b/patches/server-unmapped/0001/0499-Fix-piston-physics-inconsistency-MC-188840.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 11 Jun 2020 17:29:42 -0700 +Subject: [PATCH] Fix piston physics inconsistency - MC-188840 + +Pistons invoke physics when they move blocks. The physics can cause +tnt blocks to ignite. However, pistons (when storing the blocks they "moved") +don't actually go back to the world state sometimes to check if something +like that happened. As a result they end up moving the tnt like it was +never ignited. This resulted in the ability to create machines +that can duplicate tnt, called "world eaters". +This patch makes the piston logic retrieve the block state from the world +prevent this from occuring. + +This patch also sets the moved pos to air immediately after creating +the moving piston TE. This prevents the block from being updated from +other physics calls by the piston. + +Tested against the following tnt duper design: +https://www.youtube.com/watch?v=mS7xxNGhjxs + +This patch also affects every type of machine that utilises +this mechanic. For example, dead coral is removed by a physics +update when being moved while it is attached to slimeblocks. + +Standard piston machines that don't destroy or modify the +blocks they move by physics updates should be entirely +unaffected. + +This patch fixes https://bugs.mojang.com/browse/MC-188840 + +This patch also fixes rail duping and carpet duping. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 7f140333c2e62012fa572c1a061d84432426997f..b67ba8f75e4a3358d7c2462918b85b0bf9b5a922 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -434,4 +434,10 @@ public class PaperConfig { + consoleHasAllPermissions = getBoolean("settings.console-has-all-permissions", consoleHasAllPermissions); + } + ++ public static boolean allowPistonDuplication; ++ private static void allowPistonDuplication() { ++ config.set("settings.unsupported-settings.allow-piston-duplication-readme", "This setting controls if player should be able to use TNT duplication, but this also allows duplicating carpet, rails and potentially other items"); ++ allowPistonDuplication = getBoolean("settings.unsupported-settings.allow-piston-duplication", config.getBoolean("settings.unsupported-settings.allow-tnt-duplication", false)); ++ set("settings.unsupported-settings.allow-tnt-duplication", null); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/block/piston/BlockPiston.java b/src/main/java/net/minecraft/world/level/block/piston/BlockPiston.java +index e062fd288098127fae22a55562e0207ceaf50163..8aa51fb207820a7629d50b80ea821ec6cccf8b54 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/BlockPiston.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/BlockPiston.java +@@ -399,12 +399,24 @@ public class BlockPiston extends BlockDirectional { + } + + for (k = list.size() - 1; k >= 0; --k) { +- blockposition3 = (BlockPosition) list.get(k); +- iblockdata1 = world.getType(blockposition3); ++ // Paper start - fix a variety of piston desync dupes ++ boolean allowDesync = com.destroystokyo.paper.PaperConfig.allowPistonDuplication; ++ BlockPosition oldPos = blockposition3 = (BlockPosition) list.get(k); ++ iblockdata1 = allowDesync ? world.getType(oldPos) : null; ++ // Paper end - fix a variety of piston desync dupes + blockposition3 = blockposition3.shift(enumdirection1); + map.remove(blockposition3); + world.setTypeAndData(blockposition3, (IBlockData) Blocks.MOVING_PISTON.getBlockData().set(BlockPiston.FACING, enumdirection), 68); +- world.setTileEntity(blockposition3, BlockPistonMoving.a((IBlockData) list1.get(k), enumdirection, flag, false)); ++ // Paper start - fix a variety of piston desync dupes ++ if (!allowDesync) { ++ iblockdata1 = world.getType(oldPos); ++ map.replace(oldPos, iblockdata1); ++ } ++ world.setTileEntity(blockposition3, BlockPistonMoving.a(allowDesync ? list1.get(k) : iblockdata1, enumdirection, flag, false)); ++ if (!allowDesync) { ++ world.setTypeAndData(oldPos, Blocks.AIR.getBlockData(), 2 | 4 | 16 | 1024); // set air to prevent later physics updates from seeing this block ++ } ++ // Paper end - fix a variety of piston desync dupes + aiblockdata[j++] = iblockdata1; + } + +diff --git a/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java b/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java +index 8d13e60f40e1b760e9e69969dc3f37bc6c70dbe9..e70c3a8c9075b6c0bc73e6488d784dfe3b86e58d 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java +@@ -279,7 +279,7 @@ public class TileEntityPiston extends TileEntity implements ITickable { + IBlockData iblockdata = Block.b(this.a, (GeneratorAccess) this.world, this.position); + + if (iblockdata.isAir()) { +- this.world.setTypeAndData(this.position, this.a, 84); ++ this.world.setTypeAndData(this.position, this.a, com.destroystokyo.paper.PaperConfig.allowPistonDuplication ? 84 : (84 | 2)); // Paper - force notify (flag 2), it's possible the set type by the piston block (which doesn't notify) set this block to air + Block.a(this.a, iblockdata, this.world, this.position, 3); + } else { + if (iblockdata.b(BlockProperties.C) && (Boolean) iblockdata.get(BlockProperties.C)) { diff --git a/patches/server-unmapped/0001/0500-Fix-sand-duping.patch b/patches/server-unmapped/0001/0500-Fix-sand-duping.patch new file mode 100644 index 0000000000..e3e2417078 --- /dev/null +++ b/patches/server-unmapped/0001/0500-Fix-sand-duping.patch @@ -0,0 +1,37 @@ +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/EntityFallingBlock.java b/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java +index 411e3915c0aa00249aacb6658ed04309665d2fb4..62d8b53c024888aa43b8fddf8a475dfb8284a4cc 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityFallingBlock.java +@@ -103,6 +103,11 @@ public class EntityFallingBlock extends Entity { + + @Override + public void tick() { ++ // Paper start - fix sand duping ++ if (this.dead) { ++ return; ++ } ++ // Paper end - fix sand duping + if (this.block.isAir()) { + this.die(); + } else { +@@ -125,6 +130,12 @@ public class EntityFallingBlock extends Entity { + + this.move(EnumMoveType.SELF, this.getMot()); + ++ // Paper start - fix sand duping ++ if (this.dead) { ++ return; ++ } ++ // Paper end - fix sand duping ++ + // Paper start - Configurable EntityFallingBlock height nerf + if (this.world.paperConfig.fallingBlockHeightNerf != 0 && this.locY() > this.world.paperConfig.fallingBlockHeightNerf) { + if (this.dropItem && this.world.getGameRules().getBoolean(GameRules.DO_ENTITY_DROPS)) { diff --git a/patches/server-unmapped/0001/0501-Prevent-position-desync-in-playerconnection-causing-.patch b/patches/server-unmapped/0001/0501-Prevent-position-desync-in-playerconnection-causing-.patch new file mode 100644 index 0000000000..4422c552fe --- /dev/null +++ b/patches/server-unmapped/0001/0501-Prevent-position-desync-in-playerconnection-causing-.patch @@ -0,0 +1,31 @@ +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 in playerconnection 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/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 42190a8c6c2eadf05f57df50e3ca997384327b67..dd26d1ad34600250de7572c09bd4d74331142ad9 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1326,6 +1326,11 @@ public class PlayerConnection implements PacketListenerPlayIn { + + this.player.move(EnumMoveType.PLAYER, new Vec3D(d7, d8, d9)); + this.player.setOnGround(packetplayinflying.b()); // CraftBukkit - SPIGOT-5810, SPIGOT-5835: reset by this.player.move ++ // Paper start - prevent position desync ++ if (this.teleportPos != null) { ++ return; // ... thanks Mojang for letting move calls teleport across dimensions. ++ } ++ // Paper end - prevent position desync + double d12 = d8; + + d7 = d4 - this.player.locX(); diff --git a/patches/server-unmapped/0001/0502-Fix-enderdragon-exp-dupe.patch b/patches/server-unmapped/0001/0502-Fix-enderdragon-exp-dupe.patch new file mode 100644 index 0000000000..6217196c77 --- /dev/null +++ b/patches/server-unmapped/0001/0502-Fix-enderdragon-exp-dupe.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 12 Jun 2020 22:25:11 -0700 +Subject: [PATCH] Fix enderdragon exp dupe + +Properly track death stage when unloading/loading in the +dragon + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java +index 5168a40eb53565bb3028efe559601acf72bddae5..c296fcf80c2f3f210fa020416973ec8d5db541ba 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java +@@ -879,6 +879,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + public void saveData(NBTTagCompound nbttagcompound) { + super.saveData(nbttagcompound); + nbttagcompound.setInt("DragonPhase", this.bG.a().getControllerPhase().b()); ++ nbttagcompound.setInt("Paper.DeathTick", this.deathAnimationTicks); // Paper + } + + @Override +@@ -887,6 +888,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + if (nbttagcompound.hasKey("DragonPhase")) { + this.bG.setControllerPhase(DragonControllerPhase.getById(nbttagcompound.getInt("DragonPhase"))); + } ++ this.deathAnimationTicks = nbttagcompound.getInt("Paper.DeathTick"); // Paper + + } + diff --git a/patches/server-unmapped/0001/0503-Inventory-getHolder-method-without-block-snapshot.patch b/patches/server-unmapped/0001/0503-Inventory-getHolder-method-without-block-snapshot.patch new file mode 100644 index 0000000000..eeb9b62352 --- /dev/null +++ b/patches/server-unmapped/0001/0503-Inventory-getHolder-method-without-block-snapshot.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Wed, 10 Jun 2020 23:55:15 +0100 +Subject: [PATCH] Inventory getHolder method without block snapshot + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +index 45634fded9916dca35a246921efb87964c860339..c3fa97ac34e1fc61ae02f224f8afe5a0b486fb4d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +@@ -9,6 +9,7 @@ import net.minecraft.world.inventory.InventoryCrafting; + import net.minecraft.world.inventory.InventoryEnderChest; + import net.minecraft.world.inventory.InventoryMerchant; + import net.minecraft.world.level.block.entity.IHopper; ++import net.minecraft.world.level.block.entity.TileEntity; + import net.minecraft.world.level.block.entity.TileEntityBarrel; + import net.minecraft.world.level.block.entity.TileEntityBlastFurnace; + import net.minecraft.world.level.block.entity.TileEntityBrewingStand; +@@ -526,6 +527,13 @@ public class CraftInventory implements Inventory { + return inventory.getOwner(); + } + ++ // Paper start - getHolder without snapshot ++ @Override ++ public InventoryHolder getHolder(boolean useSnapshot) { ++ return inventory instanceof TileEntity ? ((TileEntity) inventory).getOwner(useSnapshot) : getHolder(); ++ } ++ // Paper end ++ + @Override + public int getMaxStackSize() { + return inventory.getMaxStackSize(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryDoubleChest.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryDoubleChest.java +index 47029af761e26453090980b9a231fd53d4238cc4..d22abb4259dfcfd3ec0e0516f87fc838a81353ce 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryDoubleChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryDoubleChest.java +@@ -64,6 +64,13 @@ public class CraftInventoryDoubleChest extends CraftInventory implements DoubleC + return new DoubleChest(this); + } + ++ // Paper start - getHolder without snapshot ++ @Override ++ public DoubleChest getHolder(boolean useSnapshot) { ++ return getHolder(); ++ } ++ // Paper end ++ + @Override + public Location getLocation() { + return getLeftSide().getLocation().add(getRightSide().getLocation()).multiply(0.5); diff --git a/patches/server-unmapped/0001/0504-Expose-Arrow-getItemStack.patch b/patches/server-unmapped/0001/0504-Expose-Arrow-getItemStack.patch new file mode 100644 index 0000000000..5bc5621e30 --- /dev/null +++ b/patches/server-unmapped/0001/0504-Expose-Arrow-getItemStack.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nesaak <52047222+Nesaak@users.noreply.github.com> +Date: Sat, 23 May 2020 10:31:11 -0400 +Subject: [PATCH] Expose Arrow getItemStack + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java b/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java +index 5ecbe9135a71dd84e0722fa9c039c272a11d206f..4cd1ddec701252269de0ce8520ee492f1e1cbacb 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java +@@ -558,6 +558,7 @@ public abstract class EntityArrow extends IProjectile { + } + } + ++ public final ItemStack getOriginalItemStack() { return getItemStack(); } // Paper - OBFHELPER - exists purely due to overrides all as protected and dont want to change them all + protected abstract ItemStack getItemStack(); + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +index ea33a36dfa6f304946f5b998eb536678a9b2f98c..2ab41a24a22e736753276b95fa0d060017cca0bc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +@@ -103,6 +103,13 @@ public class CraftArrow extends AbstractProjectile implements AbstractArrow { + getHandle().fromPlayer = EntityArrow.PickupStatus.a(status.ordinal()); + } + ++ // Paper start ++ @Override ++ public org.bukkit.craftbukkit.inventory.CraftItemStack getItemStack() { ++ return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(getHandle().getOriginalItemStack()); ++ } ++ //Paper end ++ + @Override + public void setTicksLived(int value) { + super.setTicksLived(value); diff --git a/patches/server-unmapped/0001/0505-Add-and-implement-PlayerRecipeBookClickEvent.patch b/patches/server-unmapped/0001/0505-Add-and-implement-PlayerRecipeBookClickEvent.patch new file mode 100644 index 0000000000..554173a965 --- /dev/null +++ b/patches/server-unmapped/0001/0505-Add-and-implement-PlayerRecipeBookClickEvent.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Fri, 5 Jun 2020 18:24:06 -0400 +Subject: [PATCH] Add and implement PlayerRecipeBookClickEvent + + +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index dd26d1ad34600250de7572c09bd4d74331142ad9..20c6a61101e40b09babf0ce6ad05a889d9fdfe46 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -2771,9 +2771,15 @@ public class PlayerConnection implements PacketListenerPlayIn { + PlayerConnectionUtils.ensureMainThread(packetplayinautorecipe, this, this.player.getWorldServer()); + this.player.resetIdleTimer(); + if (!this.player.isSpectator() && this.player.activeContainer.windowId == packetplayinautorecipe.b() && this.player.activeContainer.c(this.player) && this.player.activeContainer instanceof ContainerRecipeBook) { +- this.minecraftServer.getCraftingManager().getRecipe(packetplayinautorecipe.c()).ifPresent((irecipe) -> { +- ((ContainerRecipeBook) this.player.activeContainer).a(packetplayinautorecipe.d(), irecipe, this.player); +- }); ++ // Paper start - fire event for clicking recipes in the recipe book ++ com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent event = new com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent( ++ player.getBukkitEntity(), org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(packetplayinautorecipe.c()), packetplayinautorecipe.d()); ++ if (event.callEvent()) { ++ this.minecraftServer.getCraftingManager().getRecipe(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getRecipe())).ifPresent((irecipe) -> { ++ ((ContainerRecipeBook) this.player.activeContainer).a(event.isMakeAll(), irecipe, this.player); ++ }); ++ } ++ // Paper end + } + } + diff --git a/patches/server-unmapped/0001/0506-Hide-sync-chunk-writes-behind-flag.patch b/patches/server-unmapped/0001/0506-Hide-sync-chunk-writes-behind-flag.patch new file mode 100644 index 0000000000..9b9dd4ede4 --- /dev/null +++ b/patches/server-unmapped/0001/0506-Hide-sync-chunk-writes-behind-flag.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 26 Jun 2020 22:35:08 -0700 +Subject: [PATCH] Hide sync chunk writes behind flag + +Syncing writes on each write call has terrible performance +on harddrives. + +-DPaper.enable-sync-chunk-writes=true to enable + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +index b7cf02301c02ed0a6b696384e656426762ae2105..1fab9b9c7d41a0d2a551096c2c15f741a887fa2d 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -111,7 +111,7 @@ public class DedicatedServerProperties extends PropertyManager { + return MathHelper.clamp(integer, 1, 29999984); + }, 29999984); +- this.syncChunkWrites = this.getBoolean("sync-chunk-writes", true); ++ this.syncChunkWrites = this.getBoolean("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - hide behind flag + this.enableJmxMonitoring = this.getBoolean("enable-jmx-monitoring", false); + this.enableStatus = this.getBoolean("enable-status", true); + this.entityBroadcastRangePercentage = this.a("entity-broadcast-range-percentage", (integer) -> { diff --git a/patches/server-unmapped/0001/0507-Limit-lightning-strike-effect-distance.patch b/patches/server-unmapped/0001/0507-Limit-lightning-strike-effect-distance.patch new file mode 100644 index 0000000000..779333c56d --- /dev/null +++ b/patches/server-unmapped/0001/0507-Limit-lightning-strike-effect-distance.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Fri, 14 Sep 2018 17:42:08 +0200 +Subject: [PATCH] Limit lightning strike effect distance + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 1655bca0502e7b871de4addaa163536d86547a02..978062774c1db286bfb9b0ffdef19d880b1f249b 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -646,4 +646,26 @@ public class PaperWorldConfig { + delayChunkUnloadsBy *= 20; + } + } ++ ++ public double sqrMaxThunderDistance; ++ public double sqrMaxLightningImpactSoundDistance; ++ public double maxLightningFlashDistance; ++ private void lightningStrikeDistanceLimit() { ++ sqrMaxThunderDistance = getInt("lightning-strike-distance-limit.sound", -1); ++ if (sqrMaxThunderDistance > 0) { ++ sqrMaxThunderDistance *= sqrMaxThunderDistance; ++ } ++ ++ sqrMaxLightningImpactSoundDistance = getInt("lightning-strike-distance-limit.impact-sound", -1); ++ if (sqrMaxLightningImpactSoundDistance < 0) { ++ sqrMaxLightningImpactSoundDistance = 32 * 32; //Vanilla value ++ } else { ++ sqrMaxLightningImpactSoundDistance *= sqrMaxLightningImpactSoundDistance; ++ } ++ ++ maxLightningFlashDistance = getInt("lightning-strike-distance-limit.flash", -1); ++ if (maxLightningFlashDistance < 0) { ++ maxLightningFlashDistance = 512; // Vanilla value ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityLightning.java b/src/main/java/net/minecraft/world/entity/EntityLightning.java +index 8946fcd93bd785a8c21683b932aa954fbf15d566..834ced9d9b385c8f1d66355244313d62a97d9c98 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLightning.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLightning.java +@@ -76,6 +76,17 @@ public class EntityLightning extends Entity { + double deltaX = this.locX() - player.locX(); + double deltaZ = this.locZ() - player.locZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; ++ // Paper start - Limit lightning strike effect distance ++ if (distanceSquared <= this.world.paperConfig.sqrMaxLightningImpactSoundDistance) { ++ player.playerConnection.sendPacket(new PacketPlayOutNamedSoundEffect(SoundEffects.ENTITY_LIGHTNING_BOLT_IMPACT, ++ SoundCategory.WEATHER, this.locX(), this.locY(), this.locZ(), 2.0f, 0.5F + this.random.nextFloat() * 0.2F)); ++ } ++ ++ if (world.paperConfig.sqrMaxThunderDistance != -1 && distanceSquared >= world.paperConfig.sqrMaxThunderDistance) { ++ continue; ++ } ++ ++ // Paper end + if (distanceSquared > viewDistance * viewDistance) { + double deltaLength = Math.sqrt(distanceSquared); + double relativeX = player.locX() + (deltaX / deltaLength) * viewDistance; +@@ -86,7 +97,7 @@ public class EntityLightning extends Entity { + } + } + // CraftBukkit end +- this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_LIGHTNING_BOLT_IMPACT, SoundCategory.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F); ++// this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_LIGHTNING_BOLT_IMPACT, SoundCategory.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F); // Paper - Limit lightning strike effect distance (the packet is now sent from inside the loop) + } + + --this.lifeTicks; diff --git a/patches/server-unmapped/0001/0508-Add-permission-for-command-blocks.patch b/patches/server-unmapped/0001/0508-Add-permission-for-command-blocks.patch new file mode 100644 index 0000000000..7c1206137a --- /dev/null +++ b/patches/server-unmapped/0001/0508-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/PlayerInteractManager.java b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +index 7c7425f2312882540947f0fc528d123933e8fd98..192a1c894a371ecab68e5e8ec10fab62268d1a9c 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerInteractManager.java ++++ b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +@@ -386,7 +386,7 @@ public class PlayerInteractManager { + TileEntity tileentity = this.world.getTileEntity(blockposition); + Block block = iblockdata.getBlock(); + +- if ((block instanceof BlockCommand || block instanceof BlockStructure || block instanceof BlockJigsaw) && !this.player.isCreativeAndOp()) { ++ if ((block instanceof BlockCommand || block instanceof BlockStructure || block instanceof BlockJigsaw) && !this.player.isCreativeAndOp() && !(block instanceof BlockCommand && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + this.world.notify(blockposition, iblockdata, iblockdata, 3); + return false; + } else if (this.player.a((World) this.world, blockposition, this.gamemode)) { +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 20c6a61101e40b09babf0ce6ad05a889d9fdfe46..eecf23aa41c8b21fce58ff33a3124186a4fdccd1 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -798,7 +798,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + PlayerConnectionUtils.ensureMainThread(packetplayinsetcommandblock, this, this.player.getWorldServer()); + if (!this.minecraftServer.getEnableCommandBlock()) { + this.player.sendMessage(new ChatMessage("advMode.notEnabled"), SystemUtils.b); +- } else if (!this.player.isCreativeAndOp()) { ++ } else if (!this.player.isCreativeAndOp() && !this.player.isCreative() && !this.player.getBukkitEntity().hasPermission("minecraft.commandblock")) { // Paper - command block permission + this.player.sendMessage(new ChatMessage("advMode.notAllowed"), SystemUtils.b); + } else { + CommandBlockListenerAbstract commandblocklistenerabstract = null; +@@ -861,7 +861,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + PlayerConnectionUtils.ensureMainThread(packetplayinsetcommandminecart, this, this.player.getWorldServer()); + if (!this.minecraftServer.getEnableCommandBlock()) { + this.player.sendMessage(new ChatMessage("advMode.notEnabled"), SystemUtils.b); +- } else if (!this.player.isCreativeAndOp()) { ++ } else if (!this.player.isCreativeAndOp() && !this.player.isCreative() && !this.player.getBukkitEntity().hasPermission("minecraft.commandblock")) { // Paper - command block permission + this.player.sendMessage(new ChatMessage("advMode.notAllowed"), SystemUtils.b); + } else { + CommandBlockListenerAbstract commandblocklistenerabstract = packetplayinsetcommandminecart.a(this.player.world); +diff --git a/src/main/java/net/minecraft/world/level/CommandBlockListenerAbstract.java b/src/main/java/net/minecraft/world/level/CommandBlockListenerAbstract.java +index 3fcdff3649c725580456dfc965d6c83bd5afe3da..85e7957103d2b2e16e4d3a3ea0bd7de4935f61cd 100644 +--- a/src/main/java/net/minecraft/world/level/CommandBlockListenerAbstract.java ++++ b/src/main/java/net/minecraft/world/level/CommandBlockListenerAbstract.java +@@ -192,7 +192,7 @@ public abstract class CommandBlockListenerAbstract implements ICommandListener { + } + + public EnumInteractionResult a(EntityHuman entityhuman) { +- if (!entityhuman.isCreativeAndOp()) { ++ if (!entityhuman.isCreativeAndOp() && !entityhuman.isCreative() && !entityhuman.getBukkitEntity().hasPermission("minecraft.commandblock")) { // Paper - command block permission + return EnumInteractionResult.PASS; + } else { + if (entityhuman.getWorld().isClientSide) { +diff --git a/src/main/java/net/minecraft/world/level/block/BlockCommand.java b/src/main/java/net/minecraft/world/level/block/BlockCommand.java +index ad50f86d11ff22f055ca9f26cd02a84e75c7d8c8..f7d22282a59277375d146e9459f9f43962dd7d09 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockCommand.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockCommand.java +@@ -128,7 +128,7 @@ public class BlockCommand extends BlockTileEntity { + public EnumInteractionResult interact(IBlockData iblockdata, World world, BlockPosition blockposition, EntityHuman entityhuman, EnumHand enumhand, MovingObjectPositionBlock movingobjectpositionblock) { + TileEntity tileentity = world.getTileEntity(blockposition); + +- if (tileentity instanceof TileEntityCommand && entityhuman.isCreativeAndOp()) { ++ if (tileentity instanceof TileEntityCommand && (entityhuman.isCreativeAndOp() || (entityhuman.isCreative() && entityhuman.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + entityhuman.a((TileEntityCommand) tileentity); + return EnumInteractionResult.a(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 525ebf961e5da0687183a5e2ead23ed92cbd9d79..a4a809f302c5ff9c76cde5fc0add2ceec1bdf9b5 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(ROOT + ".nbt.copy", "Gives the user the ability to copy NBT in creative", org.bukkit.permissions.PermissionDefault.TRUE, parent); + DefaultPermissions.registerPermission(ROOT + ".debugstick", "Gives the user the ability to use the debug stick in creative", org.bukkit.permissions.PermissionDefault.OP, parent); + DefaultPermissions.registerPermission(ROOT + ".debugstick.always", "Gives the user the ability to use the debug stick in all game modes", org.bukkit.permissions.PermissionDefault.FALSE, parent); ++ DefaultPermissions.registerPermission(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-unmapped/0001/0509-Ensure-Entity-AABB-s-are-never-invalid.patch b/patches/server-unmapped/0001/0509-Ensure-Entity-AABB-s-are-never-invalid.patch new file mode 100644 index 0000000000..df29f83aa1 --- /dev/null +++ b/patches/server-unmapped/0001/0509-Ensure-Entity-AABB-s-are-never-invalid.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 May 2020 22:12:46 -0400 +Subject: [PATCH] Ensure Entity AABB's are never invalid + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index da2b5bfd3966ded2d5dde0d65646583576a088c5..c50b89ee8f16821923933025bf19243771dd1604 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -66,6 +66,7 @@ import net.minecraft.world.INamableTileEntity; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.entity.animal.EntityAnimal; + import net.minecraft.world.entity.animal.EntityFish; ++import net.minecraft.world.entity.decoration.EntityHanging; + import net.minecraft.world.entity.item.EntityItem; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.entity.vehicle.EntityBoat; +@@ -479,7 +480,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + public void setPosition(double d0, double d1, double d2) { + this.setPositionRaw(d0, d1, d2); +- this.a(this.size.a(d0, d1, d2)); ++ //this.a(this.size.a(d0, d1, d2)); // Paper - move into setPositionRaw + if (valid) ((WorldServer) world).chunkCheck(this); // CraftBukkit + } + +@@ -2994,6 +2995,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return new AxisAlignedBB(vec3d, vec3d1); + } + ++ public final void setBoundingBox(AxisAlignedBB axisalignedbb) { a(axisalignedbb); } // Paper - OBFHELPER + public void a(AxisAlignedBB axisalignedbb) { + // CraftBukkit start - block invalid bounding boxes + double minX = axisalignedbb.minX, +@@ -3432,6 +3434,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public void setPositionRaw(double d0, double d1, double d2) { ++ // Paper start - never allow AABB to become desynced from position ++ // hanging has its own special logic ++ if (!(this instanceof EntityHanging) && (this.loc.x != d0 || this.loc.y != d1 || this.loc.z != d2)) { ++ this.setBoundingBox(this.size.a(d0, d1, d2)); ++ } ++ // Paper end + if (this.loc.x != d0 || this.loc.y != d1 || this.loc.z != d2) { + this.loc = new Vec3D(d0, d1, d2); + int i = MathHelper.floor(d0); diff --git a/patches/server-unmapped/0001/0510-Optimize-WorldBorder-collision-checks-and-air.patch b/patches/server-unmapped/0001/0510-Optimize-WorldBorder-collision-checks-and-air.patch new file mode 100644 index 0000000000..b153f9d172 --- /dev/null +++ b/patches/server-unmapped/0001/0510-Optimize-WorldBorder-collision-checks-and-air.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 10 May 2020 22:49:05 -0400 +Subject: [PATCH] Optimize WorldBorder collision checks and air + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c50b89ee8f16821923933025bf19243771dd1604..27e5ba64ed6406c1ece318bf79fca0f261a77818 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -909,7 +909,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + AxisAlignedBB axisalignedbb = this.getBoundingBox(); + VoxelShapeCollision voxelshapecollision = VoxelShapeCollision.a(this); + VoxelShape voxelshape = this.world.getWorldBorder().c(); +- Stream stream = VoxelShapes.c(voxelshape, VoxelShapes.a(axisalignedbb.shrink(1.0E-7D)), OperatorBoolean.AND) ? Stream.empty() : Stream.of(voxelshape); ++ Stream stream = !this.world.getWorldBorder().isInBounds(axisalignedbb) ? Stream.empty() : Stream.of(voxelshape); // Paper + Stream stream1 = this.world.c(this, axisalignedbb.b(vec3d), (entity) -> { + return true; + }); +diff --git a/src/main/java/net/minecraft/world/level/VoxelShapeSpliterator.java b/src/main/java/net/minecraft/world/level/VoxelShapeSpliterator.java +index d0cc8677f2be422722160fee9b71894b5ddd3186..03584572fa5bf0d96fc4cecece573547f9c94cea 100644 +--- a/src/main/java/net/minecraft/world/level/VoxelShapeSpliterator.java ++++ b/src/main/java/net/minecraft/world/level/VoxelShapeSpliterator.java +@@ -143,10 +143,10 @@ public class VoxelShapeSpliterator extends AbstractSpliterator { + AxisAlignedBB axisalignedbb = this.a.getBoundingBox(); + + if (!a(worldborder, axisalignedbb)) { +- VoxelShape voxelshape = worldborder.c(); +- +- if (!b(voxelshape, axisalignedbb) && a(voxelshape, axisalignedbb)) { +- consumer.accept(voxelshape); ++ // Paper start ++ if (worldborder.isInBounds(axisalignedbb.shrink(1.0E-7D)) && !worldborder.isInBounds(axisalignedbb.grow(1.0E-7D))) { ++ consumer.accept(worldborder.asVoxelShape()); ++ // Paper end + return true; + } + } +diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +index 50e2085766caabec1125ca24a2117549efd1a354..bedaa9dd6390e81df5872c2dd6e202a038367bf6 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -52,6 +52,7 @@ public class WorldBorder { + return (double) chunkcoordintpair.f() > this.e() && (double) chunkcoordintpair.d() < this.g() && (double) chunkcoordintpair.g() > this.f() && (double) chunkcoordintpair.e() < this.h(); + } + ++ public final boolean isInBounds(AxisAlignedBB aabb) { return this.a(aabb); } // Paper - OBFHELPER + public boolean a(AxisAlignedBB axisalignedbb) { + return axisalignedbb.maxX > this.e() && axisalignedbb.minX < this.g() && axisalignedbb.maxZ > this.f() && axisalignedbb.minZ < this.h(); + } +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +index 2d7405d1fa7c8f378bebe86f5d0de57a129ed92d..858d4689e618c72250447adb61e0bcc3c156f8f3 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +@@ -252,7 +252,7 @@ public final class VoxelShapes { + IBlockData iblockdata = iworldreader.getTypeIfLoaded(blockposition_mutableblockposition); // Paper + if (iblockdata == null) return 0.0D; // Paper + +- if ((k2 != 1 || iblockdata.d()) && (k2 != 2 || iblockdata.a(Blocks.MOVING_PISTON))) { ++ if (!iblockdata.isAir() && (k2 != 1 || iblockdata.d()) && (k2 != 2 || iblockdata.a(Blocks.MOVING_PISTON))) { // Paper + d0 = iblockdata.b((IBlockAccess) iworldreader, blockposition_mutableblockposition, voxelshapecollision).a(enumdirection_enumaxis2, axisalignedbb.d((double) (-blockposition_mutableblockposition.getX()), (double) (-blockposition_mutableblockposition.getY()), (double) (-blockposition_mutableblockposition.getZ())), d0); + if (Math.abs(d0) < 1.0E-7D) { + return 0.0D; diff --git a/patches/server-unmapped/0001/0511-Fix-Per-World-Difficulty-Remembering-Difficulty.patch b/patches/server-unmapped/0001/0511-Fix-Per-World-Difficulty-Remembering-Difficulty.patch new file mode 100644 index 0000000000..49bfb0b709 --- /dev/null +++ b/patches/server-unmapped/0001/0511-Fix-Per-World-Difficulty-Remembering-Difficulty.patch @@ -0,0 +1,86 @@ +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 5b9f03f3118e6d19ed5c3d41a94b06172d594a81..50c1c6cc53eead25d03ed45427cb9fb80bc2fc36 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1645,11 +1645,15 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Sun, 28 Jun 2020 19:27:20 -0400 +Subject: [PATCH] Paper dumpitem command + +Let's you quickly view the item in your hands NBT data + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index d165e8c232c38ba2e2faf93c60c8a127bb74c9b6..4b3efe01750d79bcc27a42b5a145d9aa6b124d18 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -9,6 +9,7 @@ import com.google.common.collect.Iterables; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import net.minecraft.core.BlockPosition; ++import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.network.protocol.game.PacketPlayOutLightUpdate; + import net.minecraft.resources.MinecraftKey; + import com.google.gson.JsonObject; +@@ -36,7 +37,9 @@ import org.bukkit.command.CommandSender; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; + import org.bukkit.entity.Player; ++import org.bukkit.inventory.ItemStack; + + import java.io.File; + import java.io.FileOutputStream; +@@ -59,7 +62,7 @@ import java.util.stream.Collectors; + + public class PaperCommand extends Command { + private static final String BASE_PERM = "bukkit.command.paper."; +- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo", "fixlight").build(); ++ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo", "fixlight", "dumpitem").build(); + + public PaperCommand(String name) { + super(name); +@@ -168,6 +171,9 @@ public class PaperCommand extends Command { + case "reload": + doReload(sender); + break; ++ case "dumpitem": ++ doDumpItem(sender); ++ break; + case "debug": + doDebug(sender, args); + break; +@@ -200,6 +206,19 @@ public class PaperCommand extends Command { + return true; + } + ++ private void doDumpItem(CommandSender sender) { ++ ItemStack itemInHand = ((CraftPlayer) sender).getItemInHand(); ++ net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(itemInHand); ++ NBTTagCompound tag = itemStack.getTag(); ++ if (tag != null) { ++ String nbt = org.bukkit.craftbukkit.util.CraftChatMessage.fromComponent(tag.getNbtPrettyComponent()); ++ Bukkit.getConsoleSender().sendMessage(nbt); ++ sender.sendMessage(nbt); ++ } else { ++ sender.sendMessage("Item does not have NBT"); ++ } ++ } ++ + private void doFixLight(CommandSender sender, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("Only players can use this command"); +diff --git a/src/main/java/net/minecraft/nbt/NBTBase.java b/src/main/java/net/minecraft/nbt/NBTBase.java +index d6e51f82f6df2d7058806f3e483766e18398af77..3921b99b9a4c1a867b5159668d2cd62d7463e1ff 100644 +--- a/src/main/java/net/minecraft/nbt/NBTBase.java ++++ b/src/main/java/net/minecraft/nbt/NBTBase.java +@@ -26,6 +26,7 @@ public interface NBTBase { + return this.toString(); + } + ++ default IChatBaseComponent getNbtPrettyComponent() { return this.l(); } // Paper - OBFHELPER + default IChatBaseComponent l() { + return this.a("", 0); + } diff --git a/patches/server-unmapped/0001/0513-Don-t-allow-null-UUID-s-for-chat.patch b/patches/server-unmapped/0001/0513-Don-t-allow-null-UUID-s-for-chat.patch new file mode 100644 index 0000000000..b5584efc91 --- /dev/null +++ b/patches/server-unmapped/0001/0513-Don-t-allow-null-UUID-s-for-chat.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 28 Jun 2020 19:36:55 -0400 +Subject: [PATCH] Don't allow null UUID's for chat + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java +index fefcacf27d71c67403555502685a992a5a706099..267e1baeaaed83befc7f6d6445a9416f7b8dfc0f 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutChat.java +@@ -3,6 +3,7 @@ package net.minecraft.network.protocol.game; + + import java.io.IOException; + import java.util.UUID; ++import net.minecraft.SystemUtils; + import net.minecraft.network.PacketDataSerializer; + import net.minecraft.network.chat.ChatMessageType; + import net.minecraft.network.chat.IChatBaseComponent; +@@ -21,7 +22,7 @@ public class PacketPlayOutChat implements Packet { + public PacketPlayOutChat(IChatBaseComponent ichatbasecomponent, ChatMessageType chatmessagetype, UUID uuid) { + this.a = ichatbasecomponent; + this.b = chatmessagetype; +- this.c = uuid; ++ this.c = uuid != null ? uuid : SystemUtils.getNullUUID(); // Paper + } + + @Override diff --git a/patches/server-unmapped/0001/0514-Improve-Legacy-Component-serialization-size.patch b/patches/server-unmapped/0001/0514-Improve-Legacy-Component-serialization-size.patch new file mode 100644 index 0000000000..f63ad3aa32 --- /dev/null +++ b/patches/server-unmapped/0001/0514-Improve-Legacy-Component-serialization-size.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 28 Jun 2020 19:08:41 -0400 +Subject: [PATCH] Improve Legacy Component serialization size + +Don't constantly send format: false for all formatting options when parent already +has it false + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +index 666af6cc91bd12ba5d5a846d663a5aabf861fbc4..189a416bd033c9ff390d359aa19cec515d0461e4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +@@ -46,6 +46,7 @@ public final class CraftChatMessage { + // Separate pattern with no group 3, new lines are part of previous string + private static final Pattern INCREMENTAL_PATTERN_KEEP_NEWLINES = Pattern.compile("(" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + "[0-9a-fk-orx])|((?:(?:https?):\\/\\/)?(?:[-\\w_\\.]{2,}\\.[a-z]{2,4}.*?(?=[\\.\\?!,;:]?(?:[" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + " ]|$))))", Pattern.CASE_INSENSITIVE); + // ChatColor.b does not explicitly reset, its more of empty ++ private static final ChatModifier EMPTY = ChatModifier.a.setItalic(false); // Paper - OBFHELPER + private static final ChatModifier RESET = ChatModifier.a.setBold(false).setItalic(false).setUnderline(false).setStrikethrough(false).setRandom(false); + + private final List list = new ArrayList(); +@@ -67,6 +68,7 @@ public final class CraftChatMessage { + Matcher matcher = (keepNewlines ? INCREMENTAL_PATTERN_KEEP_NEWLINES : INCREMENTAL_PATTERN).matcher(message); + String match = null; + boolean needsAdd = false; ++ boolean hasReset = false; // Paper + while (matcher.find()) { + int groupId = 0; + while ((match = matcher.group(++groupId)) == null) { +@@ -112,7 +114,26 @@ public final class CraftChatMessage { + throw new AssertionError("Unexpected message format"); + } + } else { // Color resets formatting +- modifier = RESET.setColor(format); ++ // Paper start - improve legacy formatting ++ ChatModifier previous = modifier; ++ modifier = (!hasReset ? RESET : EMPTY).setColor(format); ++ hasReset = true; ++ if (previous.isBold()) { ++ modifier = modifier.setBold(false); ++ } ++ if (previous.isItalic()) { ++ modifier = modifier.setItalic(false); ++ } ++ if (previous.isRandom()) { ++ modifier = modifier.setRandom(false); ++ } ++ if (previous.isStrikethrough()) { ++ modifier = modifier.setStrikethrough(false); ++ } ++ if (previous.isUnderlined()) { ++ modifier = modifier.setUnderline(false); ++ } ++ // Paper end + } + needsAdd = true; + break; diff --git a/patches/server-unmapped/0001/0515-Support-old-UUID-format-for-NBT.patch b/patches/server-unmapped/0001/0515-Support-old-UUID-format-for-NBT.patch new file mode 100644 index 0000000000..8ba6c36f4a --- /dev/null +++ b/patches/server-unmapped/0001/0515-Support-old-UUID-format-for-NBT.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 29 Jun 2020 03:26:17 -0400 +Subject: [PATCH] Support old UUID format for NBT + +We have stored UUID in plenty of places that did not get DFU'd + +So just look for old format and load it if it exists. + +diff --git a/src/main/java/net/minecraft/nbt/GameProfileSerializer.java b/src/main/java/net/minecraft/nbt/GameProfileSerializer.java +index 0560d115288c67e46d921ce529e603f424e601f5..50515cd9287505fcc8ab52e47393fb1dc771bfc3 100644 +--- a/src/main/java/net/minecraft/nbt/GameProfileSerializer.java ++++ b/src/main/java/net/minecraft/nbt/GameProfileSerializer.java +@@ -41,6 +41,11 @@ public final class GameProfileSerializer { + s = nbttagcompound.getString("Name"); + } + ++ // Paper start - support string UUID's ++ if (nbttagcompound.hasKeyOfType("Id", 8)) { ++ uuid = UUID.fromString(nbttagcompound.getString("Id")); ++ } ++ // Paper end + if (nbttagcompound.b("Id")) { + uuid = nbttagcompound.a("Id"); + } +diff --git a/src/main/java/net/minecraft/nbt/NBTTagCompound.java b/src/main/java/net/minecraft/nbt/NBTTagCompound.java +index 77afbaad5b2cb8d912f5404fcbd3a0970490f4f3..945df83d4a2170cdcc29cb6d7b9f0f5f3940cb96 100644 +--- a/src/main/java/net/minecraft/nbt/NBTTagCompound.java ++++ b/src/main/java/net/minecraft/nbt/NBTTagCompound.java +@@ -142,6 +142,12 @@ public class NBTTagCompound implements NBTBase { + + public void setUUID(String prefix, UUID uuid) { a(prefix, uuid); } // Paper - OBFHELPER + public void a(String s, UUID uuid) { ++ // Paper start - support old format ++ if (this.hasKeyOfType(s + "Most", 99) && this.hasKeyOfType(s + "Least", 99)) { ++ this.map.remove(s + "Most"); ++ this.map.remove(s + "Least"); ++ } ++ // Paper end + this.map.put(s, GameProfileSerializer.a(uuid)); + } + +@@ -151,11 +157,21 @@ public class NBTTagCompound implements NBTBase { + */ + public UUID getUUID(String prefix) { return a(prefix); } // Paper - OBFHELPER + public UUID a(String s) { ++ // Paper start - support old format ++ if (!hasKeyOfType(s, 11) && this.hasKeyOfType(s + "Most", 99) && this.hasKeyOfType(s + "Least", 99)) { ++ return new UUID(this.getLong(s + "Most"), this.getLong(s + "Least")); ++ } ++ // Paper end + return GameProfileSerializer.a(this.get(s)); + } + + public final boolean hasUUID(String s) { return this.b(s); } // Paper - OBFHELPER + public boolean b(String s) { ++ // Paper start - support old format ++ if (this.hasKeyOfType(s + "Most", 99) && this.hasKeyOfType(s + "Least", 99)) { ++ return true; ++ } ++ // Paper end + NBTBase nbtbase = this.get(s); + + return nbtbase != null && nbtbase.b() == NBTTagIntArray.a && ((NBTTagIntArray) nbtbase).getInts().length == 4; diff --git a/patches/server-unmapped/0001/0516-Clean-up-duplicated-GameProfile-Properties.patch b/patches/server-unmapped/0001/0516-Clean-up-duplicated-GameProfile-Properties.patch new file mode 100644 index 0000000000..7309e30816 --- /dev/null +++ b/patches/server-unmapped/0001/0516-Clean-up-duplicated-GameProfile-Properties.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 1 Jul 2020 03:12:06 -0400 +Subject: [PATCH] Clean up duplicated GameProfile Properties + +We had a bug where we accidently cloned properties resulting in skulls +growing to large sizes and preventing login. + +This now automatically cleans up the extra properties. + +diff --git a/src/main/java/net/minecraft/nbt/GameProfileSerializer.java b/src/main/java/net/minecraft/nbt/GameProfileSerializer.java +index 50515cd9287505fcc8ab52e47393fb1dc771bfc3..a654ea9e43c4e7dd9cde1c47d0b07834f47aa751 100644 +--- a/src/main/java/net/minecraft/nbt/GameProfileSerializer.java ++++ b/src/main/java/net/minecraft/nbt/GameProfileSerializer.java +@@ -60,8 +60,8 @@ public final class GameProfileSerializer { + while (iterator.hasNext()) { + String s1 = (String) iterator.next(); + NBTTagList nbttaglist = nbttagcompound1.getList(s1, 10); +- +- for (int i = 0; i < nbttaglist.size(); ++i) { ++ if (nbttaglist.size() == 0) continue; // Paper - remove duplicate properties ++ for (int i = nbttaglist.size() - 1; i < nbttaglist.size(); ++i) { // Paper - remove duplicate properties + NBTTagCompound nbttagcompound2 = nbttaglist.getCompound(i); + String s2 = nbttagcompound2.getString("Value"); + +@@ -247,7 +247,7 @@ public final class GameProfileSerializer { + Optional optional = iblockstate.b(nbttagcompound.getString(s)); + + if (optional.isPresent()) { +- return (IBlockDataHolder) s0.set(iblockstate, (Comparable) optional.get()); ++ return s0.set(iblockstate, optional.get()); // Paper - decompile error + } else { + GameProfileSerializer.LOGGER.warn("Unable to read property: {} with value: {} for blockstate: {}", s, nbttagcompound.getString(s), nbttagcompound1.toString()); + return s0; +@@ -277,8 +277,8 @@ public final class GameProfileSerializer { + return nbttagcompound; + } + +- private static > String a(IBlockState iblockstate, Comparable comparable) { +- return iblockstate.a(comparable); ++ private static > String a(IBlockState iblockstate, Comparable comparable) {// Paper - decompile error ++ return iblockstate.a((T) comparable);// Paper - decompile error + } + + public static NBTTagCompound a(DataFixer datafixer, DataFixTypes datafixtypes, NBTTagCompound nbttagcompound, int i) { +diff --git a/src/main/java/net/minecraft/world/item/ItemSkullPlayer.java b/src/main/java/net/minecraft/world/item/ItemSkullPlayer.java +index 70207d1ca5b971f829911b1231160f4664062da0..47eed24fa28db8c12ce98b6b72ca1671e13e9f52 100644 +--- a/src/main/java/net/minecraft/world/item/ItemSkullPlayer.java ++++ b/src/main/java/net/minecraft/world/item/ItemSkullPlayer.java +@@ -59,6 +59,18 @@ public class ItemSkullPlayer extends ItemBlockWallable { + return true; + } else { + // CraftBukkit start ++ // Paper start - clean up old duplicated properties ++ NBTTagCompound properties = nbttagcompound.getCompound("SkullOwner").getCompound("Properties"); ++ for (String key : properties.getKeys()) { ++ net.minecraft.nbt.NBTTagList values = properties.getList(key, 10); ++ if (values.size() > 1) { ++ net.minecraft.nbt.NBTBase texture = values.get(values.size() - 1); ++ values = new net.minecraft.nbt.NBTTagList(); ++ values.add(texture); ++ properties.set(key, values); ++ } ++ } ++ // Paper end + net.minecraft.nbt.NBTTagList textures = nbttagcompound.getCompound("SkullOwner").getCompound("Properties").getList("textures", 10); // Safe due to method contracts + for (int i = 0; i < textures.size(); i++) { + if (textures.get(i) instanceof NBTTagCompound && !((NBTTagCompound) textures.get(i)).hasKeyOfType("Signature", 8) && ((NBTTagCompound) textures.get(i)).getString("Value").trim().isEmpty()) { diff --git a/patches/server-unmapped/0001/0517-Convert-legacy-attributes-in-Item-Meta.patch b/patches/server-unmapped/0001/0517-Convert-legacy-attributes-in-Item-Meta.patch new file mode 100644 index 0000000000..025b86e719 --- /dev/null +++ b/patches/server-unmapped/0001/0517-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 43fa8d3c573540682fc87ee2bf8d61ba80d3732d..673948947bd918c1dbb6c4c99486b4200e3c09fe 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +@@ -13,6 +13,20 @@ import org.bukkit.craftbukkit.util.CraftNamespacedKey; + public class CraftAttributeMap implements Attributable { + + private final AttributeMapBase 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(AttributeMapBase 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 8f8dccd6fb2e49d65383d6e8f3fc5ffbabd2b7a5..8398ac45e988c621b3e059d75101185513ddd9ab 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -481,7 +481,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + AttributeModifier attribMod = CraftAttributeInstance.convert(nmsModifier); + +- String attributeName = entry.getString(ATTRIBUTES_IDENTIFIER.NBT); ++ String attributeName = CraftAttributeMap.convertIfNeeded(entry.getString(ATTRIBUTES_IDENTIFIER.NBT)); // Paper + if (attributeName == null || attributeName.isEmpty()) { + continue; + } diff --git a/patches/server-unmapped/0001/0518-Remove-some-streams-from-structures.patch b/patches/server-unmapped/0001/0518-Remove-some-streams-from-structures.patch new file mode 100644 index 0000000000..67b43bca11 --- /dev/null +++ b/patches/server-unmapped/0001/0518-Remove-some-streams-from-structures.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Mon, 29 Jun 2020 17:03:06 -0400 +Subject: [PATCH] Remove some streams from structures + +This showed up a lot in the spark profiler, should have a low-medium performance improvement. + +diff --git a/src/main/java/net/minecraft/world/level/StructureManager.java b/src/main/java/net/minecraft/world/level/StructureManager.java +index 6fe276b29aa9fe1b312ef2773484fa780506fded..07dcfd56af1014ad159828dd9ee2d89c2010b9f4 100644 +--- a/src/main/java/net/minecraft/world/level/StructureManager.java ++++ b/src/main/java/net/minecraft/world/level/StructureManager.java +@@ -2,6 +2,7 @@ + package net.minecraft.world.level; + + import com.mojang.datafixers.DataFixUtils; ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; // Paper + import java.util.stream.Stream; + import javax.annotation.Nullable; + import net.minecraft.core.BaseBlockPosition; +@@ -12,11 +13,12 @@ import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.IStructureAccess; + import net.minecraft.world.level.levelgen.GeneratorSettings; + import net.minecraft.world.level.levelgen.feature.StructureGenerator; ++import net.minecraft.world.level.levelgen.structure.StructurePiece; + import net.minecraft.world.level.levelgen.structure.StructureStart; + + public class StructureManager { + +- private final GeneratorAccess a; ++ private final GeneratorAccess a; public GeneratorAccess getLevel() { return a; } // Paper - OBFHELPER + private final GeneratorSettings b; + + public StructureManager(GeneratorAccess generatoraccess, GeneratorSettings generatorsettings) { +@@ -42,6 +44,20 @@ public class StructureManager { + }); + } + ++ // Paper start - remove structure streams ++ public java.util.List> getFeatureStarts(SectionPosition sectionPosition, StructureGenerator structureGenerator) { ++ java.util.List> list = new ObjectArrayList<>(); ++ for (Long curLong: getLevel().getChunkAt(sectionPosition.a(), sectionPosition.c(), ChunkStatus.STRUCTURE_REFERENCES).b(structureGenerator)) { ++ SectionPosition sectionPosition1 = SectionPosition.a(new ChunkCoordIntPair(curLong), 0); ++ StructureStart structurestart = a(sectionPosition1, structureGenerator, getLevel().getChunkAt(sectionPosition1.a(), sectionPosition1.c(), ChunkStatus.STRUCTURE_STARTS)); ++ if (structurestart != null && structurestart.e()) { ++ list.add(structurestart); ++ } ++ } ++ return list; ++ } ++ // Paper end ++ + @Nullable + public StructureStart a(SectionPosition sectionposition, StructureGenerator structuregenerator, IStructureAccess istructureaccess) { + return istructureaccess.a(structuregenerator); +@@ -60,13 +76,21 @@ public class StructureManager { + } + + public StructureStart a(BlockPosition blockposition, boolean flag, StructureGenerator structuregenerator) { +- return (StructureStart) DataFixUtils.orElse(this.a(SectionPosition.a(blockposition), structuregenerator).filter((structurestart) -> { +- return structurestart.c().b((BaseBlockPosition) blockposition); +- }).filter((structurestart) -> { +- return !flag || structurestart.d().stream().anyMatch((structurepiece) -> { +- return structurepiece.g().b((BaseBlockPosition) blockposition); +- }); +- }).findFirst(), StructureStart.a); ++ // Paper start - remove structure streams ++ for (StructureStart structurestart : getFeatureStarts(SectionPosition.a(blockposition), structuregenerator)) { ++ if (structurestart.c().b(blockposition)) { ++ if (!flag) { ++ return structurestart; ++ } ++ for (StructurePiece structurepiece : structurestart.d()) { ++ if (structurepiece.g().b(blockposition)) { ++ return structurestart; ++ } ++ } ++ } ++ } ++ return StructureStart.a; ++ // Paper end + } + + // Spigot start +diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeBase.java b/src/main/java/net/minecraft/world/level/biome/BiomeBase.java +index 7147cdda756ccb3d4f6880802128f68601783883..15096a9c2719b8b4c099f62d0a1c808e56b63a8e 100644 +--- a/src/main/java/net/minecraft/world/level/biome/BiomeBase.java ++++ b/src/main/java/net/minecraft/world/level/biome/BiomeBase.java +@@ -39,6 +39,7 @@ import net.minecraft.world.level.levelgen.WorldGenStage; + import net.minecraft.world.level.levelgen.feature.StructureGenerator; + import net.minecraft.world.level.levelgen.feature.WorldGenFeatureConfigured; + import net.minecraft.world.level.levelgen.structure.StructureBoundingBox; ++import net.minecraft.world.level.levelgen.structure.StructureStart; + import net.minecraft.world.level.levelgen.surfacebuilders.WorldGenSurfaceComposite; + import net.minecraft.world.level.levelgen.synth.NoiseGenerator3; + import net.minecraft.world.level.material.Fluid; +@@ -238,9 +239,11 @@ public final class BiomeBase { + int l1 = j1 << 4; + + try { +- structuremanager.a(SectionPosition.a(blockposition), structuregenerator).forEach((structurestart) -> { +- structurestart.a(regionlimitedworldaccess, structuremanager, chunkgenerator, seededrandom, new StructureBoundingBox(k1, l1, k1 + 15, l1 + 15), new ChunkCoordIntPair(i1, j1)); +- }); ++ // Paper start - remove structure streams ++ for (StructureStart structureStart : structuremanager.getFeatureStarts(SectionPosition.a(blockposition), structuregenerator)) { ++ structureStart.a(regionlimitedworldaccess, structuremanager, chunkgenerator, seededrandom, new StructureBoundingBox(k1, l1, k1 + 15, l1 + 15), new ChunkCoordIntPair(i1, j1)); ++ } ++ // Paper end + } catch (Exception exception) { + CrashReport crashreport = CrashReport.a(exception, "Feature placement"); + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/ChunkGeneratorAbstract.java b/src/main/java/net/minecraft/world/level/levelgen/ChunkGeneratorAbstract.java +index 700b32322e8d0fbb8ec2824e50a340be16b48f81..369fb0bda22f02e76b901b6eb8990651c53c7577 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/ChunkGeneratorAbstract.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/ChunkGeneratorAbstract.java +@@ -40,6 +40,7 @@ import net.minecraft.world.level.levelgen.feature.structures.WorldGenFeatureDefi + import net.minecraft.world.level.levelgen.feature.structures.WorldGenFeatureDefinedStructurePoolTemplate; + import net.minecraft.world.level.levelgen.structure.StructureBoundingBox; + import net.minecraft.world.level.levelgen.structure.StructurePiece; ++import net.minecraft.world.level.levelgen.structure.StructureStart; + import net.minecraft.world.level.levelgen.structure.WorldGenFeaturePillagerOutpostPoolPiece; + import net.minecraft.world.level.levelgen.synth.NoiseGenerator; + import net.minecraft.world.level.levelgen.synth.NoiseGenerator3; +@@ -455,7 +456,7 @@ public final class ChunkGeneratorAbstract extends ChunkGenerator { + while (iterator.hasNext()) { + StructureGenerator structuregenerator = (StructureGenerator) iterator.next(); + +- structuremanager.a(SectionPosition.a(chunkcoordintpair, 0), structuregenerator).forEach((structurestart) -> { ++ for (StructureStart structurestart : structuremanager.getFeatureStarts(SectionPosition.a(chunkcoordintpair, 0), structuregenerator)) { // Paper - remove structure streams + Iterator iterator1 = structurestart.d().iterator(); + + while (iterator1.hasNext()) { +@@ -487,7 +488,7 @@ public final class ChunkGeneratorAbstract extends ChunkGenerator { + } + } + +- }); ++ } // Paper - remove structure streams + } + + double[][][] adouble = new double[2][this.p + 1][this.o + 1]; diff --git a/patches/server-unmapped/0001/0519-Remove-streams-from-classes-related-villager-gossip.patch b/patches/server-unmapped/0001/0519-Remove-streams-from-classes-related-villager-gossip.patch new file mode 100644 index 0000000000..2e8b227335 --- /dev/null +++ b/patches/server-unmapped/0001/0519-Remove-streams-from-classes-related-villager-gossip.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Wed, 1 Jul 2020 18:01:49 -0400 +Subject: [PATCH] Remove streams from classes related villager gossip + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java b/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java +index 9cc3a18636a356977577076e96cb7be706c61abf..7d34d1157786227ac210edc1595a024ccb61a3e9 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java +@@ -9,6 +9,7 @@ import com.mojang.serialization.DynamicOps; + import it.unimi.dsi.fastutil.objects.Object2IntMap; + import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; + import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; // Paper + import it.unimi.dsi.fastutil.objects.ObjectIterator; + import java.util.Arrays; + import java.util.Collection; +@@ -51,8 +52,21 @@ public class Reputation { + }); + } + ++ // Paper start - Remove streams from reputation ++ private List decompress() { ++ List list = new ObjectArrayList<>(); ++ for (Map.Entry entry : getReputations().entrySet()) { ++ for (Reputation.b cur : entry.getValue().decompress(entry.getKey())) { ++ if (cur.a() != 0) ++ list.add(cur); ++ } ++ } ++ return list; ++ } ++ // Paper end ++ + private Collection a(Random random, int i) { +- List list = (List) this.c().collect(Collectors.toList()); ++ List list = decompress(); // Paper - Remove streams from reputation + + if (list.isEmpty()) { + return Collections.emptyList(); +@@ -119,7 +133,7 @@ public class Reputation { + } + + public Dynamic a(DynamicOps dynamicops) { +- return new Dynamic(dynamicops, dynamicops.createList(this.c().map((reputation_b) -> { ++ return new Dynamic(dynamicops, dynamicops.createList(this.decompress().stream().map((reputation_b) -> { + return reputation_b.a(dynamicops); + }).map(Dynamic::getValue))); + } +@@ -144,18 +158,30 @@ public class Reputation { + + public static class a { // Paper - make public + +- private final Object2IntMap a; ++ private final Object2IntMap a; private Object2IntMap getEntries() { return a; } // Paper - OBFHELPER + + public a() { // Paper - make public - update CraftVillager setReputation on change + this.a = new Object2IntOpenHashMap(); + } + + public int a(Predicate predicate) { +- return this.a.object2IntEntrySet().stream().filter((entry) -> { +- return predicate.test(entry.getKey()); +- }).mapToInt((entry) -> { +- return entry.getIntValue() * ((ReputationType) entry.getKey()).g; +- }).sum(); ++ // Paper start - Remove streams from reputation ++ int weight = 0; ++ for (Object2IntMap.Entry entry : getEntries().object2IntEntrySet()) { ++ if (predicate.test(entry.getKey())) { ++ weight += entry.getIntValue() * entry.getKey().getWeight(); ++ } ++ } ++ return weight; ++ } ++ ++ public List decompress(UUID uuid) { ++ List list = new ObjectArrayList<>(); ++ for (Object2IntMap.Entry entry : getEntries().object2IntEntrySet()) { ++ list.add(new Reputation.b(uuid, entry.getKey(), entry.getIntValue())); ++ } ++ return list; ++ // Paper - end + } + + public Stream a(UUID uuid) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/ReputationType.java b/src/main/java/net/minecraft/world/entity/ai/gossip/ReputationType.java +index 89651e6e3bb1cfbb8eb8a120b3c3e553cd831a68..6036476aac4a81152e8b142445041ecdb2d6e5d1 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/ReputationType.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/ReputationType.java +@@ -11,7 +11,7 @@ public enum ReputationType { + MAJOR_NEGATIVE("major_negative", -5, 100, 10, 10), MINOR_NEGATIVE("minor_negative", -1, 200, 20, 20), MINOR_POSITIVE("minor_positive", 1, 200, 1, 5), MAJOR_POSITIVE("major_positive", 5, 100, 0, 100), TRADING("trading", 1, 25, 2, 20); + + public final String f; +- public final int g; ++ public final int g; public int getWeight() { return g; } // Paper - OBFHELPER + public final int h; + public final int i; + public final int j; diff --git a/patches/server-unmapped/0001/0520-Support-components-in-ItemMeta.patch b/patches/server-unmapped/0001/0520-Support-components-in-ItemMeta.patch new file mode 100644 index 0000000000..6f1e33b76d --- /dev/null +++ b/patches/server-unmapped/0001/0520-Support-components-in-ItemMeta.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Sat, 6 Jun 2020 18:13:42 +0200 +Subject: [PATCH] Support components in ItemMeta + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 8398ac45e988c621b3e059d75101185513ddd9ab..5c1319a86f6314c1d0a979af34424ee025a8030f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -875,11 +875,23 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return CraftChatMessage.fromJSONComponent(displayName); + } + ++ // Paper start ++ @Override ++ public net.md_5.bungee.api.chat.BaseComponent[] getDisplayNameComponent() { ++ return displayName == null ? new net.md_5.bungee.api.chat.BaseComponent[0] : net.md_5.bungee.chat.ComponentSerializer.parse(displayName); ++ } ++ // Paper end + @Override + public final void setDisplayName(String name) { + this.displayName = CraftChatMessage.fromStringOrNullToJSON(name); + } + ++ // Paper start ++ @Override ++ public void setDisplayNameComponent(net.md_5.bungee.api.chat.BaseComponent[] component) { ++ this.displayName = net.md_5.bungee.chat.ComponentSerializer.toString(component); ++ } ++ // Paper end + @Override + public boolean hasDisplayName() { + return displayName != null; +@@ -1022,6 +1034,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return this.lore == null ? null : new ArrayList(Lists.transform(this.lore, CraftChatMessage::fromJSONComponent)); + } + ++ // Paper start ++ @Override ++ public List getLoreComponents() { ++ return this.lore == null ? null : new ArrayList<>(this.lore.stream().map(entry -> ++ net.md_5.bungee.chat.ComponentSerializer.parse(entry) ++ ).collect(java.util.stream.Collectors.toList())); ++ } ++ // Paper end + @Override + public void setLore(List lore) { + if (lore == null || lore.isEmpty()) { +@@ -1036,6 +1056,21 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + ++ // Paper start ++ @Override ++ public void setLoreComponents(List lore) { ++ if (lore == null) { ++ this.lore = null; ++ } else { ++ if (this.lore == null) { ++ safelyAdd(lore, this.lore = new ArrayList<>(lore.size()), false); ++ } else { ++ this.lore.clear(); ++ safelyAdd(lore, this.lore, false); ++ } ++ } ++ } ++ // Paper end + @Override + public boolean hasCustomModelData() { + return customModelData != null; +@@ -1497,6 +1532,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + + for (Object object : addFrom) { ++ // Paper start - support components ++ if(object instanceof net.md_5.bungee.api.chat.BaseComponent[]) { ++ addTo.add(net.md_5.bungee.chat.ComponentSerializer.toString((net.md_5.bungee.api.chat.BaseComponent[]) object)); ++ } else ++ // Paper end + if (!(object instanceof String)) { + if (object != null) { + throw new IllegalArgumentException(addFrom + " cannot contain non-string " + object.getClass().getName()); diff --git a/patches/server-unmapped/0001/0521-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch b/patches/server-unmapped/0001/0521-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch new file mode 100644 index 0000000000..c5d87e17db --- /dev/null +++ b/patches/server-unmapped/0001/0521-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 3 Jul 2020 15:03:33 -0700 +Subject: [PATCH] Improve EntityTargetLivingEntityEvent for 1.16 mobs + +CraftBukkit has a bug in their implementation and is incorrectly handling forget +Also adds more target reasons for why it forgot target. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorAttackTargetForget.java b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorAttackTargetForget.java +index ad2e4be7483d39aeb7be3727aba6a5fe89ea9602..05c507270ff5b4c0d684d0fc4a69ee47618cd281 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorAttackTargetForget.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorAttackTargetForget.java +@@ -33,15 +33,15 @@ public class BehaviorAttackTargetForget extends Beha + + protected void a(WorldServer worldserver, E e0, long i) { + if (a((EntityLiving) e0)) { +- this.d(e0); ++ this.d(e0, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET); // Paper + } else if (this.c(e0)) { +- this.d(e0); ++ this.d(e0, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_DIED); // Paper + } else if (this.a(e0)) { +- this.d(e0); ++ this.d(e0, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_OTHER_LEVEL); // Paper + } else if (!IEntitySelector.f.test(this.b(e0))) { +- this.d(e0); ++ this.d(e0, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID); // Paper + } else if (this.b.test(this.b(e0))) { +- this.d(e0); ++ this.d(e0, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID); // Paper + } + } + +@@ -65,17 +65,20 @@ public class BehaviorAttackTargetForget extends Beha + return optional.isPresent() && !((EntityLiving) optional.get()).isAlive(); + } + +- private void d(E e0) { ++ private void d(E e0, EntityTargetEvent.TargetReason reason) { + // CraftBukkit start +- EntityLiving old = e0.getBehaviorController().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); +- EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(e0, null, (old != null && !old.isAlive()) ? EntityTargetEvent.TargetReason.TARGET_DIED : EntityTargetEvent.TargetReason.FORGOT_TARGET); ++ // Paper start - fix this event ++ //EntityLiving old = e0.getBehaviorController().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); ++ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(e0, null, reason); + if (event.isCancelled()) { + return; + } +- if (event.getTarget() != null) { ++ // comment out, bad logic - bad ++ /*if (event.getTarget() != null) { + e0.getBehaviorController().setMemory(MemoryModuleType.ATTACK_TARGET, ((CraftLivingEntity) event.getTarget()).getHandle()); + return; +- } ++ }*/ ++ // Paper end + // CraftBukkit end + e0.getBehaviorController().removeMemory(MemoryModuleType.ATTACK_TARGET); + } diff --git a/patches/server-unmapped/0001/0522-Add-entity-liquid-API.patch b/patches/server-unmapped/0001/0522-Add-entity-liquid-API.patch new file mode 100644 index 0000000000..abbec1c1e7 --- /dev/null +++ b/patches/server-unmapped/0001/0522-Add-entity-liquid-API.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 2 Jul 2020 18:11:43 -0500 +Subject: [PATCH] Add entity liquid API + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 27e5ba64ed6406c1ece318bf79fca0f261a77818..743d4725c0a26a8abd0a98eed2ec45ffba6211ad 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1166,12 +1166,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return this.inWater; + } + +- private boolean isInRain() { ++ public boolean isInRain() { // Paper - private -> public + BlockPosition blockposition = this.getChunkCoordinates(); + + return this.world.isRainingAt(blockposition) || this.world.isRainingAt(new BlockPosition((double) blockposition.getX(), this.getBoundingBox().maxY, (double) blockposition.getZ())); + } + ++ public final boolean isInBubbleColumn() { return k(); } // Paper - OBFHELPER + private boolean k() { + return this.world.getType(this.getChunkCoordinates()).a(Blocks.BUBBLE_COLUMN); + } +@@ -1185,6 +1186,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return this.isInWater() || this.isInRain() || this.k(); + } + ++ public final boolean isInWaterOrBubbleColumn() { return aH(); } // Paper - OBFHELPER + public boolean aH() { + return this.isInWater() || this.k(); + } +@@ -1327,6 +1329,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + return this.O == tag; + } + ++ public final boolean isInLava() { return aQ(); } // Paper - OBFHELPER + public boolean aQ() { + return !this.justCreated && this.M.getDouble(TagsFluid.LAVA) > 0.0D; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 266b2cbd6bfaf10743929a1eeb9732a5d1fb4c62..387f6f6fa9bbb1cce544cfb907f68c7993752dd7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1137,5 +1137,29 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason getEntitySpawnReason() { + return getHandle().spawnReason; + } ++ ++ public boolean isInRain() { ++ return getHandle().isInRain(); ++ } ++ ++ public boolean isInBubbleColumn() { ++ return getHandle().isInBubbleColumn(); ++ } ++ ++ public boolean isInWaterOrRain() { ++ return getHandle().isInWaterOrRain(); ++ } ++ ++ public boolean isInWaterOrBubbleColumn() { ++ return getHandle().isInWaterOrBubbleColumn(); ++ } ++ ++ public boolean isInWaterOrRainOrBubbleColumn() { ++ return getHandle().isInWaterOrRainOrBubble(); ++ } ++ ++ public boolean isInLava() { ++ return getHandle().isInLava(); ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0523-Update-itemstack-legacy-name-and-lore.patch b/patches/server-unmapped/0001/0523-Update-itemstack-legacy-name-and-lore.patch new file mode 100644 index 0000000000..0c77743434 --- /dev/null +++ b/patches/server-unmapped/0001/0523-Update-itemstack-legacy-name-and-lore.patch @@ -0,0 +1,79 @@ +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 0468f80b7f52ee45fc9364470b23f80f7cd0cb57..158c7a30af8326fa419af391167c07d75b8611ac 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -27,6 +27,7 @@ import net.minecraft.core.IRegistry; + import net.minecraft.nbt.NBTBase; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTTagList; ++import net.minecraft.nbt.NBTTagString; + import net.minecraft.network.chat.ChatComponentText; + import net.minecraft.network.chat.ChatComponentUtils; + import net.minecraft.network.chat.ChatHoverable; +@@ -137,6 +138,44 @@ public final class ItemStack { + list.sort((Comparator) enchantSorter); // Paper + } catch (Exception ignored) {} + } ++ ++ private void processText() { ++ NBTTagCompound display = getSubTag("display"); ++ if (display != null) { ++ if (display.hasKeyOfType("Name", 8)) { ++ String json = display.getString("Name"); ++ if (json != null && json.contains("\u00A7")) { ++ try { ++ display.set("Name", convert(json)); ++ } catch (JsonParseException jsonparseexception) { ++ display.remove("Name"); ++ } ++ } ++ } ++ if (display.hasKeyOfType("Lore", 9)) { ++ NBTTagList list = display.getList("Lore", 8); ++ 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 (JsonParseException e) { ++ list.set(index, NBTTagString.create(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(new ChatComponentText("")))); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ private NBTTagString convert(String json) { ++ IChatBaseComponent component = IChatBaseComponent.ChatSerializer.jsonToComponent(json); ++ if (component instanceof ChatComponentText && component.getText().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(component.getText())[0]; ++ } ++ return NBTTagString.create(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(component)); ++ } + // Paper end + + public ItemStack(IMaterial imaterial) { +@@ -182,6 +221,7 @@ public final class ItemStack { + // CraftBukkit start - make defensive copy as this data may be coming from the save thread + this.tag = (NBTTagCompound) nbttagcompound.getCompound("tag").clone(); + processEnchantOrder(this.tag); // Paper ++ processText(); // Paper + this.getItem().b(this.tag); + // CraftBukkit end + } +@@ -665,6 +705,7 @@ public final class ItemStack { + } + } + ++ @Nullable public NBTTagCompound getSubTag(String s) { return b(s); } // Paper - OBFHELPER + @Nullable + public NBTTagCompound b(String s) { + return this.tag != null && this.tag.hasKeyOfType(s, 10) ? this.tag.getCompound(s) : null; diff --git a/patches/server-unmapped/0001/0524-Spawn-player-in-correct-world-on-login.patch b/patches/server-unmapped/0001/0524-Spawn-player-in-correct-world-on-login.patch new file mode 100644 index 0000000000..d3f4b9b844 --- /dev/null +++ b/patches/server-unmapped/0001/0524-Spawn-player-in-correct-world-on-login.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Wyatt Childers +Date: Fri, 3 Jul 2020 14:57:05 -0400 +Subject: [PATCH] Spawn player in correct world on login + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index fa9fae3979caaaef16a21d7407bb05e796963d7c..b33e610fee244e4791f6a88e8699c999913cb2d3 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -196,7 +196,18 @@ public abstract class PlayerList { + }String lastKnownName = s; // Paper + // CraftBukkit end + +- if (nbttagcompound != null) { ++ // Paper start - move logic in Entity to here, to use bukkit supplied world UUID. ++ if (nbttagcompound != null && nbttagcompound.hasKey("WorldUUIDMost") && nbttagcompound.hasKey("WorldUUIDLeast")) { ++ UUID uid = new UUID(nbttagcompound.getLong("WorldUUIDMost"), nbttagcompound.getLong("WorldUUIDLeast")); ++ org.bukkit.World bWorld = org.bukkit.Bukkit.getServer().getWorld(uid); ++ if (bWorld != null) { ++ resourcekey = ((CraftWorld) bWorld).getHandle().getDimensionKey(); ++ } else { ++ resourcekey = World.OVERWORLD; ++ } ++ } else if (nbttagcompound != null) { ++ // Vanilla migration support ++ // Paper end + DataResult dataresult = DimensionManager.a(new Dynamic(DynamicOpsNBT.a, nbttagcompound.get("Dimension"))); + Logger logger = PlayerList.LOGGER; + diff --git a/patches/server-unmapped/0001/0525-Add-PrepareResultEvent.patch b/patches/server-unmapped/0001/0525-Add-PrepareResultEvent.patch new file mode 100644 index 0000000000..58aa8cd289 --- /dev/null +++ b/patches/server-unmapped/0001/0525-Add-PrepareResultEvent.patch @@ -0,0 +1,164 @@ +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/Container.java b/src/main/java/net/minecraft/world/inventory/Container.java +index e9733fd9dac89d31dbad391cb22a8c84216045db..e7c29d194d5c3e3b1b79228758f7a3d8aa060fbd 100644 +--- a/src/main/java/net/minecraft/world/inventory/Container.java ++++ b/src/main/java/net/minecraft/world/inventory/Container.java +@@ -142,6 +142,7 @@ public abstract class Container { + return nonnulllist; + } + ++ public final void notifyListeners() { this.c(); } // Paper - OBFHELPER + public void c() { + int i; + +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java b/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java +index ff618bbb3fc4acfce51f5e5e6a504a63e9ad77cd..ae5674ae9c539720a657838a640050cd3b4dc5b5 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java +@@ -307,6 +307,7 @@ public class ContainerAnvil extends ContainerAnvilAbstract { + } + + this.e(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 2); // Paper + } + + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerAnvilAbstract.java b/src/main/java/net/minecraft/world/inventory/ContainerAnvilAbstract.java +index 5f176b1a02b217cf907f3a41d637e9059c43e928..8d6a23beb44cce2e4e13a814047da5f84d35830d 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerAnvilAbstract.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerAnvilAbstract.java +@@ -74,6 +74,7 @@ public abstract class ContainerAnvilAbstract extends Container { + super.a(iinventory); + if (iinventory == this.repairInventory) { + this.e(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 2); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerCartography.java b/src/main/java/net/minecraft/world/inventory/ContainerCartography.java +index ce3c22d6f05703874eedf634331ea92ef4c039bf..031e75dc1f3dd6fc1cee684e8e7a105b3e402127 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerCartography.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerCartography.java +@@ -148,6 +148,7 @@ public class ContainerCartography extends Container { + this.a(itemstack, itemstack1, itemstack2); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 2); // Paper + } + + private void a(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2) { +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java b/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java +index fad7355a549aef811bca43be198af3d1c0a53980..1d5dcbbd3870eb8e1013a97f6ce882bfc096bf95 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java +@@ -157,6 +157,7 @@ public class ContainerGrindstone extends Container { + super.a(iinventory); + if (iinventory == this.craftInventory) { + this.e(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 2); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerLoom.java b/src/main/java/net/minecraft/world/inventory/ContainerLoom.java +index 350a9b6525a95a00e72e836f1cc9e1a6b99fdd7a..7980930cc712e37a788f894bf2d2ee2b1cfc1196 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerLoom.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerLoom.java +@@ -190,7 +190,8 @@ public class ContainerLoom extends Container { + } + + this.j(); +- this.c(); ++ //this.c(); // Paper - done below ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerSmithing.java b/src/main/java/net/minecraft/world/inventory/ContainerSmithing.java +index 3791a8c2b4b5879e4ee331b7e427c9c1c1e4a623..a51280e5affbe399d276b4ee409b196dddfd40ac 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerSmithing.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerSmithing.java +@@ -80,6 +80,7 @@ public class ContainerSmithing extends ContainerAnvilAbstract { + // CraftBukkit end + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 2); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerStonecutter.java b/src/main/java/net/minecraft/world/inventory/ContainerStonecutter.java +index cfcb2469569edd51bbb74ca8d7a35a1ec0ecb434..1589d9ca201d386d11d9fd57fa8ba6848bae215c 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerStonecutter.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerStonecutter.java +@@ -156,6 +156,7 @@ public class ContainerStonecutter extends Container { + this.a(iinventory, itemstack); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 1); // Paper + } + + private void a(IInventory iinventory, ItemStack itemstack) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 0b6ce7a3c077982a5f341baf3049e6ce66eaa194..2e4dc2fb42b10243ddacbf5af606910a5769ea01 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1512,19 +1512,44 @@ public class CraftEventFactory { + return event; + } + +- public static PrepareAnvilEvent callPrepareAnvilEvent(InventoryView view, ItemStack item) { +- PrepareAnvilEvent event = new PrepareAnvilEvent(view, CraftItemStack.asCraftMirror(item).clone()); +- event.getView().getPlayer().getServer().getPluginManager().callEvent(event); ++ // Paper start - disable this method, handled below ++ public static void callPrepareAnvilEvent(InventoryView view, ItemStack item) { // Paper - verify nothing uses return - handled below in PrepareResult ++ PrepareAnvilEvent event = new PrepareAnvilEvent(view, CraftItemStack.asCraftMirror(item)); // Paper - remove clone ++ //event.getView().getPlayer().getServer().getPluginManager().callEvent(event); // disable event + event.getInventory().setItem(2, event.getResult()); +- return event; ++ //return event; // Paper + } ++ // Paper end + +- public static PrepareSmithingEvent callPrepareSmithingEvent(InventoryView view, ItemStack item) { +- PrepareSmithingEvent event = new PrepareSmithingEvent(view, CraftItemStack.asCraftMirror(item).clone()); +- event.getView().getPlayer().getServer().getPluginManager().callEvent(event); ++ // Paper start - disable this method, handled in callPrepareResultEvent ++ public static void callPrepareSmithingEvent(InventoryView view, ItemStack item) { // Paper - verify nothing uses return - handled below in PrepareResult ++ PrepareSmithingEvent event = new PrepareSmithingEvent(view, CraftItemStack.asCraftMirror(item)); // Paper - remove clone ++ //event.getView().getPlayer().getServer().getPluginManager().callEvent(event); // Paper - disable event + event.getInventory().setItem(2, event.getResult()); +- return event; ++ //return event; // Paper + } ++ // Paper end ++ ++ // Paper start - support specific overrides for prepare result ++ public static void callPrepareResultEvent(Container container, int resultSlot) { ++ 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 com.destroystokyo.paper.event.inventory.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.notifyListeners(); ++ } ++ // Paper end + + /** + * Mob spawner event. diff --git a/patches/server-unmapped/0001/0526-Allow-delegation-to-vanilla-chunk-gen.patch b/patches/server-unmapped/0001/0526-Allow-delegation-to-vanilla-chunk-gen.patch new file mode 100644 index 0000000000..84f508bd38 --- /dev/null +++ b/patches/server-unmapped/0001/0526-Allow-delegation-to-vanilla-chunk-gen.patch @@ -0,0 +1,104 @@ +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/net/minecraft/world/level/chunk/ChunkConverter.java b/src/main/java/net/minecraft/world/level/chunk/ChunkConverter.java +index 60ecd3a92af0f1968b10bb8babfb43147ef568d3..9077b70650d70dd294f53a1ef73e86e28ceaece9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkConverter.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkConverter.java +@@ -36,7 +36,7 @@ import org.apache.logging.log4j.Logger; + public class ChunkConverter { + + private static final Logger LOGGER = LogManager.getLogger(); +- public static final ChunkConverter a = new ChunkConverter(); ++ public static final ChunkConverter a = new ChunkConverter(); public static ChunkConverter getEmptyConverter() { return a; } // Paper - obfhelper + private static final EnumDirection8[] c = EnumDirection8.values(); + private final EnumSet d; + private final int[][] e; +@@ -322,7 +322,7 @@ public class ChunkConverter { + if ((Integer) iblockdata.get(BlockProperties.an) >= j) { + generatoraccess.setTypeAndData(blockposition, (IBlockData) iblockdata.set(BlockProperties.an, j), 18); + if (i != 7) { +- EnumDirection[] aenumdirection = null.f; ++ EnumDirection[] aenumdirection = f; // Paper - decomp fix + int k = aenumdirection.length; + + for (int l = 0; l < k; ++l) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index d0c951878600a4227e558bc68d68098c59fb7b2b..f8d040ba6c8b3a1294a8ac8d55cbd09705d4be76 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2027,6 +2027,32 @@ public final class CraftServer implements Server { + return new CraftChunkData(world); + } + ++ // Paper start ++ @Override ++ public ChunkGenerator.ChunkData createVanillaChunkData(World world, int x, int z) { ++ // get empty object ++ CraftChunkData data = (CraftChunkData) createChunkData(world); ++ // do bunch of vanilla shit ++ net.minecraft.server.level.WorldServer nmsWorld = ((CraftWorld) world).getHandle(); ++ net.minecraft.world.level.chunk.ProtoChunk protoChunk = new net.minecraft.world.level.chunk.ProtoChunk(new net.minecraft.world.level.ChunkCoordIntPair(x, z), net.minecraft.world.level.chunk.ChunkConverter.getEmptyConverter(), nmsWorld); ++ List list = new ArrayList<>(); ++ list.add(protoChunk); ++ net.minecraft.server.level.RegionLimitedWorldAccess genRegion = new net.minecraft.server.level.RegionLimitedWorldAccess(nmsWorld, list); ++ // call vanilla generator, one feature after another. Order here is important! ++ net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator = nmsWorld.getChunkProvider().chunkGenerator; ++ if (chunkGenerator instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator) { ++ chunkGenerator = ((org.bukkit.craftbukkit.generator.CustomChunkGenerator) chunkGenerator).delegate; ++ } ++ chunkGenerator.createBiomes(nmsWorld.r().b(IRegistry.ay), protoChunk); ++ chunkGenerator.buildNoise(genRegion, nmsWorld.getStructureManager(), protoChunk); ++ chunkGenerator.buildBase(genRegion, protoChunk); ++ // copy over generated sections ++ data.setRawChunkData(protoChunk.getSections()); ++ // hooray! ++ return data; ++ } ++ // Paper end ++ + @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/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +index afca0038bb74ac53f07a25729a3c1542e244c6fd..d02281f954aac8d8b65f5d36ec70f0352e4c7cdd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +@@ -19,7 +19,7 @@ import org.bukkit.material.MaterialData; + */ + public final class CraftChunkData implements ChunkGenerator.ChunkData { + private final int maxHeight; +- private final ChunkSection[] sections; ++ private ChunkSection[] sections; // Paper - remove final + private Set tiles; + private World world; // Paper - Anti-Xray - Add world + +@@ -168,6 +168,12 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + return sections; + } + ++ // Paper start ++ public void setRawChunkData(ChunkSection[] sections) { ++ this.sections = sections; ++ } ++ // Paper end ++ + Set getTiles() { + return tiles; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java +index 7ce7e13032fe43b8998410937d07bfffa6f01527..fb7f68887efb1de8ff9167dd110fcb52627e9206 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java +@@ -33,7 +33,7 @@ import org.bukkit.generator.ChunkGenerator.ChunkData; + + public class CustomChunkGenerator extends InternalChunkGenerator { + +- private final net.minecraft.world.level.chunk.ChunkGenerator delegate; ++ public final net.minecraft.world.level.chunk.ChunkGenerator delegate; // Paper - public + private final ChunkGenerator generator; + private final WorldServer world; + private final Random random = new Random(); diff --git a/patches/server-unmapped/0001/0527-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch b/patches/server-unmapped/0001/0527-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch new file mode 100644 index 0000000000..af390a029e --- /dev/null +++ b/patches/server-unmapped/0001/0527-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/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 850225509a5398ddcc9335bf88e99bde662bfc91..a5b731bab16bf05351fee7e64ab6ae4d830309f7 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -3041,7 +3041,7 @@ public abstract class EntityLiving extends Entity { + Entity entity = this.getVehicle(); + + super.stopRiding(suppressCancellation); // Paper - suppress +- if (entity != null && entity != this.getVehicle() && !this.world.isClientSide) { ++ if (entity != null && entity != this.getVehicle() && !this.world.isClientSide && entity.valid) { // Paper - don't process on world gen + this.a(entity); + } + diff --git a/patches/server-unmapped/0001/0528-Optimize-NetworkManager-Exception-Handling.patch b/patches/server-unmapped/0001/0528-Optimize-NetworkManager-Exception-Handling.patch new file mode 100644 index 0000000000..267d81608f --- /dev/null +++ b/patches/server-unmapped/0001/0528-Optimize-NetworkManager-Exception-Handling.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Sun, 5 Jul 2020 22:38:18 -0400 +Subject: [PATCH] Optimize NetworkManager Exception Handling + + +diff --git a/src/main/java/net/minecraft/network/EnumProtocol.java b/src/main/java/net/minecraft/network/EnumProtocol.java +index a892521db1197369bf6363bd2f5da24bf53643ab..46d8471ba98a5f2c659d35dd86c4c20c67f1ceac 100644 +--- a/src/main/java/net/minecraft/network/EnumProtocol.java ++++ b/src/main/java/net/minecraft/network/EnumProtocol.java +@@ -286,6 +286,7 @@ public enum EnumProtocol { + + @Nullable + public Packet a(int i) { ++ if (i < 0 || i >= this.b.size()) return null; // Paper + Supplier> supplier = (Supplier) this.b.get(i); + + return supplier != null ? (Packet) supplier.get() : null; +diff --git a/src/main/java/net/minecraft/network/PacketSplitter.java b/src/main/java/net/minecraft/network/PacketSplitter.java +index 2c7de7ab6da2106394ec668cd7cb9be1f8dabeb3..e81bfd35b9a745eeb1457ceda5fda320654bd89a 100644 +--- a/src/main/java/net/minecraft/network/PacketSplitter.java ++++ b/src/main/java/net/minecraft/network/PacketSplitter.java +@@ -9,11 +9,21 @@ import java.util.List; + + public class PacketSplitter extends ByteToMessageDecoder { + ++ private final byte[] lenBuf = new byte[3]; // Paper + public PacketSplitter() {} + + protected void decode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, List list) throws Exception { ++ // Paper start - if channel is not active just discard the packet ++ if (!channelhandlercontext.channel().isActive()) { ++ bytebuf.skipBytes(bytebuf.readableBytes()); ++ return; ++ } ++ // Paper end + bytebuf.markReaderIndex(); +- byte[] abyte = new byte[3]; ++ // Paper start - reuse temporary length buffer ++ byte[] abyte = lenBuf; ++ java.util.Arrays.fill(abyte, (byte) 0); ++ // Paper end + + for (int i = 0; i < abyte.length; ++i) { + if (!bytebuf.isReadable()) { +diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java +index b644c91cecd8a347319dfe8c8923fd05919a9795..4c583593de4dc8ab27b3e1187f4dfe1740ef07a7 100644 +--- a/src/main/java/net/minecraft/network/protocol/Packet.java ++++ b/src/main/java/net/minecraft/network/protocol/Packet.java +@@ -2,8 +2,10 @@ package net.minecraft.network.protocol; + + import io.netty.channel.ChannelFuture; // Paper + import java.io.IOException; ++import net.minecraft.network.NetworkManager; + import net.minecraft.network.PacketDataSerializer; + import net.minecraft.network.PacketListener; ++import net.minecraft.server.level.EntityPlayer; + + public interface Packet { + +diff --git a/src/main/java/net/minecraft/network/protocol/PlayerConnectionUtils.java b/src/main/java/net/minecraft/network/protocol/PlayerConnectionUtils.java +index e47da20ab8ce4da34755e105bf55d8542fb50138..67d8fe8ad036a9252c774bb6a914c8ec79981876 100644 +--- a/src/main/java/net/minecraft/network/protocol/PlayerConnectionUtils.java ++++ b/src/main/java/net/minecraft/network/protocol/PlayerConnectionUtils.java +@@ -1,6 +1,9 @@ + package net.minecraft.network.protocol; + ++import net.minecraft.network.NetworkManager; + import net.minecraft.network.PacketListener; ++import net.minecraft.network.chat.ChatComponentText; ++import net.minecraft.network.protocol.game.PacketPlayOutKickDisconnect; + import net.minecraft.server.CancelledPacketHandleException; + import net.minecraft.server.level.WorldServer; + import net.minecraft.util.thread.IAsyncTaskHandler; +@@ -31,6 +34,21 @@ public class PlayerConnectionUtils { + try (Timing ignored = timing.startTiming()) { // Paper - timings + packet.a(t0); + } // Paper - timings ++ // Paper start ++ catch (Exception e) { ++ NetworkManager networkmanager = t0.a(); ++ if (networkmanager.getPlayer() != null) { ++ LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getName(), networkmanager.getSocketAddress(), e); ++ } else { ++ LOGGER.error("Error whilst processing packet {} for connection from {}", packet, networkmanager.getSocketAddress(), e); ++ } ++ ChatComponentText error = new ChatComponentText("Packet processing error"); ++ networkmanager.sendPacket(new PacketPlayOutKickDisconnect(error), (future) -> { ++ networkmanager.close(error); ++ }); ++ networkmanager.stopReading(); ++ } ++ // Paper end + } else { + PlayerConnectionUtils.LOGGER.debug("Ignoring packet due to disconnection: " + packet); + } diff --git a/patches/server-unmapped/0001/0529-Fix-Concurrency-issue-in-WeightedList.patch b/patches/server-unmapped/0001/0529-Fix-Concurrency-issue-in-WeightedList.patch new file mode 100644 index 0000000000..9a961b6d30 --- /dev/null +++ b/patches/server-unmapped/0001/0529-Fix-Concurrency-issue-in-WeightedList.patch @@ -0,0 +1,148 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 6 Jul 2020 18:36:41 -0400 +Subject: [PATCH] Fix Concurrency issue in WeightedList + +if multiple threads from worldgen sort at same time, it will crash. +So make a copy of the list for sorting purposes. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorGate.java b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorGate.java +index dc926f7e59fa350902d4a24aefc3df3eac7d75db..2d4345de154fb2d31f34695672ebdb4dac31b181 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorGate.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorGate.java +@@ -17,7 +17,7 @@ public class BehaviorGate extends Behavior { + private final Set> b; + private final BehaviorGate.Order c; + private final BehaviorGate.Execution d; +- private final WeightedList> e = new WeightedList<>(); ++ private final WeightedList> e = new WeightedList<>(false); // Paper - don't use a clone + + public BehaviorGate(Map, MemoryStatus> map, Set> set, BehaviorGate.Order behaviorgate_order, BehaviorGate.Execution behaviorgate_execution, List, Integer>> list) { + super(map); +@@ -65,10 +65,9 @@ public class BehaviorGate extends Behavior { + }).forEach((behavior) -> { + behavior.g(worldserver, e0, i); + }); +- Set set = this.b; + BehaviorController behaviorcontroller = e0.getBehaviorController(); + +- set.forEach(behaviorcontroller::removeMemory); ++ this.b.forEach(behaviorcontroller::removeMemory); // Paper - decomp fix + } + + @Override +@@ -115,7 +114,7 @@ public class BehaviorGate extends Behavior { + + private final Consumer> c; + +- private Order(Consumer consumer) { ++ private Order(Consumer> consumer) { // Paper - decomp fix + this.c = consumer; + } + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java +index f6f8c68ff3642e28901094e8b501fcf8ec2cecd7..1ca9b0595ae9d914d23590ec0b0c2e857c39b250 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/WeightedList.java +@@ -6,7 +6,7 @@ import com.mojang.serialization.Codec; + import com.mojang.serialization.DataResult; + import com.mojang.serialization.Dynamic; + import com.mojang.serialization.DynamicOps; +-import com.mojang.serialization.OptionalDynamic; ++ + import java.util.Comparator; + import java.util.List; + import java.util.Random; +@@ -14,26 +14,32 @@ import java.util.stream.Stream; + + public class WeightedList { + +- protected final List> a; ++ protected final List> list; // Paper - decompile conflict + private final Random b; ++ private final boolean isUnsafe; // Paper + +- public WeightedList() { +- this(Lists.newArrayList()); ++ // Paper start - add useClone option ++ public WeightedList() { this(true); } ++ public WeightedList(boolean isUnsafe) { ++ this(Lists.newArrayList(), isUnsafe); + } + +- private WeightedList(List> list) { ++ private WeightedList(List> list) { this(list, true); } ++ private WeightedList(List> list, boolean isUnsafe) { ++ this.isUnsafe = isUnsafe; ++ // Paper end + this.b = new Random(); +- this.a = Lists.newArrayList(list); ++ this.list = Lists.newArrayList(list); // Paper - decompile conflict + } + + public static Codec> a(Codec codec) { +- return WeightedList.a.a(codec).listOf().xmap(WeightedList::new, (weightedlist) -> { +- return weightedlist.a; ++ return WeightedList.a.a(codec).listOf().xmap(WeightedList::new, (weightedlist) -> { // Paper - decompile conflict ++ return weightedlist.list; // Paper - decompile conflict + }); + } + + public WeightedList a(U u0, int i) { +- this.a.add(new WeightedList.a<>(u0, i)); ++ this.list.add(new WeightedList.a<>(u0, i)); // Paper - decompile conflict + return this; + } + +@@ -42,21 +48,20 @@ public class WeightedList { + } + + public WeightedList a(Random random) { +- this.a.forEach((weightedlist_a) -> { +- weightedlist_a.a(random.nextFloat()); +- }); +- this.a.sort(Comparator.comparingDouble((object) -> { +- return ((WeightedList.a) object).c(); +- })); +- return this; ++ // Paper start - make concurrent safe, work off a clone of the list ++ List> list = isUnsafe ? new java.util.ArrayList>(this.list) : this.list; ++ list.forEach((weightedlist_a) -> weightedlist_a.a(random.nextFloat())); ++ list.sort(Comparator.comparingDouble(a::c)); ++ return isUnsafe ? new WeightedList<>(list, isUnsafe) : this; ++ // Paper end + } + + public boolean b() { +- return this.a.isEmpty(); ++ return this.list.isEmpty(); // Paper - decompile conflict + } + + public Stream c() { +- return this.a.stream().map(WeightedList.a::a); ++ return this.list.stream().map(WeightedList.a::a); // Paper - decompile conflict + } + + public U b(Random random) { +@@ -64,7 +69,7 @@ public class WeightedList { + } + + public String toString() { +- return "WeightedList[" + this.a + "]"; ++ return "WeightedList[" + this.list + "]"; // Paper - decompile conflict + } + + public static class a { +@@ -98,11 +103,7 @@ public class WeightedList { + return new Codec>() { + public DataResult, T>> decode(DynamicOps dynamicops, T t0) { + Dynamic dynamic = new Dynamic(dynamicops, t0); +- OptionalDynamic optionaldynamic = dynamic.get("data"); +- Codec codec1 = codec; +- +- codec.getClass(); +- return optionaldynamic.flatMap(codec1::parse).map((object) -> { ++ return dynamic.get("data").flatMap(codec::parse).map((object) -> { // Paper - decompile error + return new WeightedList.a<>(object, dynamic.get("weight").asInt(1)); + }).map((weightedlist_a) -> { + return Pair.of(weightedlist_a, dynamicops.empty()); diff --git a/patches/server-unmapped/0001/0530-Optimize-the-advancement-data-player-iteration-to-be.patch b/patches/server-unmapped/0001/0530-Optimize-the-advancement-data-player-iteration-to-be.patch new file mode 100644 index 0000000000..434cd1b5f5 --- /dev/null +++ b/patches/server-unmapped/0001/0530-Optimize-the-advancement-data-player-iteration-to-be.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Wyatt Childers +Date: Sat, 4 Jul 2020 23:07:43 -0400 +Subject: [PATCH] Optimize the advancement data player iteration to be O(N) + rather than O(N^2) + + +diff --git a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java +index 7d37626277823d5db05189c20bb1ebf91aa2a286..e1d3f7e63c731f9376900a86abe17282e9b55ded 100644 +--- a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java ++++ b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java +@@ -457,6 +457,16 @@ public class AdvancementDataPlayer { + } + + private void e(Advancement advancement) { ++ // Paper start ++ e(advancement, IterationEntryPoint.ROOT); ++ } ++ private enum IterationEntryPoint { ++ ROOT, ++ ITERATOR, ++ PARENT_OF_ITERATOR ++ } ++ private void e(Advancement advancement, IterationEntryPoint entryPoint) { ++ // Paper end + boolean flag = this.f(advancement); + boolean flag1 = this.h.contains(advancement); + +@@ -472,15 +482,23 @@ public class AdvancementDataPlayer { + } + + if (flag != flag1 && advancement.b() != null) { +- this.e(advancement.b()); ++ // Paper start - If we're not coming from an iterator consider this to be a root entry, otherwise ++ // market that we're entering from the parent of an iterator. ++ this.e(advancement.b(), entryPoint == IterationEntryPoint.ITERATOR ? IterationEntryPoint.PARENT_OF_ITERATOR : IterationEntryPoint.ROOT); + } + ++ // If this is true, we've went through a child iteration, entered the parent, processed the parent ++ // and are about to reprocess the children. Stop processing here to prevent O(N^2) processing. ++ if (entryPoint == IterationEntryPoint.PARENT_OF_ITERATOR) { ++ return; ++ } // Paper end ++ + Iterator iterator = advancement.e().iterator(); + + while (iterator.hasNext()) { + Advancement advancement1 = (Advancement) iterator.next(); + +- this.e(advancement1); ++ this.e(advancement1, IterationEntryPoint.ITERATOR); // Paper - Mark this call as being from iteration + } + + } diff --git a/patches/server-unmapped/0001/0531-Fix-arrows-never-despawning-MC-125757.patch b/patches/server-unmapped/0001/0531-Fix-arrows-never-despawning-MC-125757.patch new file mode 100644 index 0000000000..38c97d91fd --- /dev/null +++ b/patches/server-unmapped/0001/0531-Fix-arrows-never-despawning-MC-125757.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 8 Jul 2020 11:24:30 -0500 +Subject: [PATCH] Fix arrows never despawning MC-125757 + +This forces the despawn counter to start ticking regardless of +state after the arrow has been alive for 200 ticks (10 seconds) +instead of getting stuck in a never despawn state (bubble columns, +etc). + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java b/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java +index 4cd1ddec701252269de0ce8520ee492f1e1cbacb..2f8b3587f527620152609d5be342b328a7621e0f 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java +@@ -171,6 +171,7 @@ public abstract class EntityArrow extends IProjectile { + + ++this.c; + } else { ++ if (ticksLived > 200) this.tickDespawnCounter(); // Paper - tick despawnCounter regardless after 10 seconds + this.c = 0; + Vec3D vec3d2 = this.getPositionVector(); + +@@ -292,6 +293,7 @@ public abstract class EntityArrow extends IProjectile { + + } + ++ protected final void tickDespawnCounter() { this.h(); } // Paper - OBFHELPER + protected void h() { + ++this.despawnCounter; + if (this.despawnCounter >= (fromPlayer == PickupStatus.CREATIVE_ONLY ? world.paperConfig.creativeArrowDespawnRate : (fromPlayer == PickupStatus.DISALLOWED ? world.paperConfig.nonPlayerArrowDespawnRate : ((this instanceof EntityThrownTrident) ? world.spigotConfig.tridentDespawnRate : world.spigotConfig.arrowDespawnRate)))) { // Spigot // Paper - TODO: Extract this to init? diff --git a/patches/server-unmapped/0001/0532-Thread-Safe-Vanilla-Command-permission-checking.patch b/patches/server-unmapped/0001/0532-Thread-Safe-Vanilla-Command-permission-checking.patch new file mode 100644 index 0000000000..2786d49de2 --- /dev/null +++ b/patches/server-unmapped/0001/0532-Thread-Safe-Vanilla-Command-permission-checking.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 11 Jul 2020 03:54:28 -0400 +Subject: [PATCH] Thread Safe Vanilla Command permission checking + +Datapacks check this on load and are built concurrently. This was breaking them badly due +to race conditions. + +Plus, .canUse we want to be safe for async anyways. + +diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +index 7ef6c99d2235eed38197aa76bc9553d7efbe52a4..c0fac7369b111e65b896a15848ae22457e5e8914 100644 +--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +@@ -75,10 +75,10 @@ public abstract class CommandNode implements Comparable> { + public synchronized boolean canUse(final S source) { + if (source instanceof CommandListenerWrapper) { + try { +- ((CommandListenerWrapper) source).currentCommand = this; ++ ((CommandListenerWrapper) source).currentCommand.set(this); // Paper + return requirement.test(source); + } finally { +- ((CommandListenerWrapper) source).currentCommand = null; ++ ((CommandListenerWrapper) source).currentCommand.set(null); // Paper + } + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/commands/CommandListenerWrapper.java b/src/main/java/net/minecraft/commands/CommandListenerWrapper.java +index b5ee789c8dfb7f413ab60902ff3d2ef0cf8273cd..8402af32cc476d7f468842eb4f34c7521d72bcc8 100644 +--- a/src/main/java/net/minecraft/commands/CommandListenerWrapper.java ++++ b/src/main/java/net/minecraft/commands/CommandListenerWrapper.java +@@ -55,7 +55,7 @@ public class CommandListenerWrapper implements ICompletionProvider, com.destroys + private final ResultConsumer l; + private final ArgumentAnchor.Anchor m; + private final Vec2F n; +- public volatile CommandNode currentCommand; // CraftBukkit ++ public ThreadLocal currentCommand = new ThreadLocal<>(); // CraftBukkit // Paper + + public CommandListenerWrapper(ICommandListener icommandlistener, Vec3D vec3d, Vec2F vec2f, WorldServer worldserver, int i, String s, IChatBaseComponent ichatbasecomponent, MinecraftServer minecraftserver, @Nullable Entity entity) { + this(icommandlistener, vec3d, vec2f, worldserver, i, s, ichatbasecomponent, minecraftserver, entity, false, (commandcontext, flag, j) -> { +@@ -172,9 +172,11 @@ public class CommandListenerWrapper implements ICompletionProvider, com.destroys + @Override + public boolean hasPermission(int i) { + // CraftBukkit start +- CommandNode currentCommand = this.currentCommand; ++ // Paper start - fix concurrency issue ++ CommandNode currentCommand = this.currentCommand.get(); + if (currentCommand != null) { + return hasPermission(i, org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(currentCommand)); ++ // Paper end + } + // CraftBukkit end + diff --git a/patches/server-unmapped/0001/0533-Move-range-check-for-block-placing-up.patch b/patches/server-unmapped/0001/0533-Move-range-check-for-block-placing-up.patch new file mode 100644 index 0000000000..097afc0b77 --- /dev/null +++ b/patches/server-unmapped/0001/0533-Move-range-check-for-block-placing-up.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 15 Jul 2020 19:34:11 -0700 +Subject: [PATCH] Move range check for block placing up + + +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 74cb85a424703e40a869568bf9c30d26a440b477..92d5a58f7d18aebf06e3cb868abf3f41825f2f84 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1662,15 +1662,19 @@ public class PlayerConnection implements PacketListenerPlayIn { + BlockPosition blockposition = movingobjectpositionblock.getBlockPosition(); + EnumDirection enumdirection = movingobjectpositionblock.getDirection(); + ++ // Paper start - move check up ++ Location eyeLoc = this.getPlayer().getEyeLocation(); ++ double reachDistance = NumberConversions.square(eyeLoc.getX() - blockposition.getX()) + NumberConversions.square(eyeLoc.getY() - blockposition.getY()) + NumberConversions.square(eyeLoc.getZ() - blockposition.getZ()); ++ if (reachDistance > (this.getPlayer().getGameMode() == org.bukkit.GameMode.CREATIVE ? CREATIVE_PLACE_DISTANCE_SQUARED : SURVIVAL_PLACE_DISTANCE_SQUARED)) { ++ return; ++ } ++ // Paper end - move check up ++ + this.player.resetIdleTimer(); + if (blockposition.getY() < this.minecraftServer.getMaxBuildHeight()) { + if (this.teleportPos == null && this.player.h((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.a((EntityHuman) this.player, blockposition)) { + // CraftBukkit start - Check if we can actually do something over this large a distance +- Location eyeLoc = this.getPlayer().getEyeLocation(); +- double reachDistance = NumberConversions.square(eyeLoc.getX() - blockposition.getX()) + NumberConversions.square(eyeLoc.getY() - blockposition.getY()) + NumberConversions.square(eyeLoc.getZ() - blockposition.getZ()); +- if (reachDistance > (this.getPlayer().getGameMode() == org.bukkit.GameMode.CREATIVE ? CREATIVE_PLACE_DISTANCE_SQUARED : SURVIVAL_PLACE_DISTANCE_SQUARED)) { +- return; +- } ++ // Paper - move check up + this.player.clearActiveItem(); // SPIGOT-4706 + // CraftBukkit end + EnumInteractionResult enuminteractionresult = this.player.playerInteractManager.a(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); diff --git a/patches/server-unmapped/0001/0534-Fix-SPIGOT-5989.patch b/patches/server-unmapped/0001/0534-Fix-SPIGOT-5989.patch new file mode 100644 index 0000000000..308e978126 --- /dev/null +++ b/patches/server-unmapped/0001/0534-Fix-SPIGOT-5989.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Wed, 15 Jul 2020 21:42:52 -0400 +Subject: [PATCH] Fix SPIGOT-5989 + +Before this fix, if a player was respawning to a respawn anchor and +the respawn location was modified away from the anchor with the +PlayerRespawnEvent, the anchor would still lose some charge. +This fixes that by checking if the modified spawn location is +still at a respawn anchor. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index b33e610fee244e4791f6a88e8699c999913cb2d3..1b78f53d09cc26fe881933df8aa1a7e77337acf1 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -83,6 +83,7 @@ import net.minecraft.world.level.EnumGamemode; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.World; + import net.minecraft.world.level.biome.BiomeManager; ++import net.minecraft.world.level.block.BlockRespawnAnchor; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.level.border.IWorldBorderListener; +@@ -850,6 +851,7 @@ public abstract class PlayerList { + // Paper start + boolean isBedSpawn = false; + boolean isRespawn = false; ++ boolean isLocAltered = false; // Paper - Fix SPIGOT-5989 + // Paper end + + // CraftBukkit start - fire PlayerRespawnEvent +@@ -860,7 +862,7 @@ public abstract class PlayerList { + Optional optional; + + if (blockposition != null) { +- optional = EntityHuman.getBed(worldserver1, blockposition, f, flag1, flag); ++ optional = EntityHuman.getBed(worldserver1, blockposition, f, flag1, true); // Paper - Fix SPIGOT-5989 + } else { + optional = Optional.empty(); + } +@@ -903,7 +905,12 @@ public abstract class PlayerList { + } + // Spigot End + +- location = respawnEvent.getRespawnLocation(); ++ // Paper start - Fix SPIGOT-5989 ++ if (!location.equals(respawnEvent.getRespawnLocation()) ) { ++ location = respawnEvent.getRespawnLocation(); ++ isLocAltered = true; ++ } ++ // Paper end + if (!flag) entityplayer.reset(); // SPIGOT-4785 + isRespawn = true; // Paper + } else { +@@ -941,8 +948,12 @@ public abstract class PlayerList { + } + // entityplayer1.syncInventory(); + entityplayer1.setHealth(entityplayer1.getHealth()); +- if (flag2) { +- entityplayer1.playerConnection.sendPacket(new PacketPlayOutNamedSoundEffect(SoundEffects.BLOCK_RESPAWN_ANCHOR_DEPLETE, SoundCategory.BLOCKS, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 1.0F, 1.0F)); ++ // Paper start - Fix SPIGOT-5989 ++ if (flag2 && !isLocAltered) { ++ IBlockData data = worldserver1.getType(blockposition); ++ worldserver1.setTypeAndData(blockposition, data.set(BlockRespawnAnchor.a, data.get(BlockRespawnAnchor.a) - 1), 3); ++ entityplayer1.playerConnection.sendPacket(new PacketPlayOutNamedSoundEffect(SoundEffects.BLOCK_RESPAWN_ANCHOR_DEPLETE, SoundCategory.BLOCKS, (double) location.getX(), (double) location.getY(), (double) location.getZ(), 1.0F, 1.0F)); ++ // Paper end + } + // Added from changeDimension + updateClient(entityplayer); // Update health, etc... diff --git a/patches/server-unmapped/0001/0535-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch b/patches/server-unmapped/0001/0535-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch new file mode 100644 index 0000000000..c659730156 --- /dev/null +++ b/patches/server-unmapped/0001/0535-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 10 Jul 2020 13:12:33 -0500 +Subject: [PATCH] Fix SPIGOT-5824 Bukkit world-container is not used + + +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 1732fc552c290d294b68d6f92f2a58d985fbef21..3dcbeec94d86f3b56eaf05676b176f60d711f9cd 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -132,11 +132,20 @@ public class Main { + return; + } + +- File file = (File) optionset.valueOf("universe"); // CraftBukkit ++ // Paper start - fix SPIGOT-5824 ++ File file; ++ File userCacheFile = new File("usercache.json"); ++ if (optionset.has("universe")) { ++ file = (File) optionset.valueOf("universe"); // CraftBukkit ++ userCacheFile = new File(file, "usercache.json"); ++ } else { ++ file = new File(bukkitConfiguration.getString("settings.world-container", ".")); ++ } ++ // Paper end - fix SPIGOT-5824 + YggdrasilAuthenticationService yggdrasilauthenticationservice = new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY); // Paper + MinecraftSessionService minecraftsessionservice = yggdrasilauthenticationservice.createMinecraftSessionService(); + GameProfileRepository gameprofilerepository = yggdrasilauthenticationservice.createProfileRepository(); +- UserCache usercache = new UserCache(gameprofilerepository, new File(file, MinecraftServer.b.getName())); ++ UserCache usercache = new UserCache(gameprofilerepository, userCacheFile); // Paper - only move usercache.json into folder if --universe is used, not world-container + // CraftBukkit start + String s = (String) Optional.ofNullable(optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName); + Convertable convertable = Convertable.a(file.toPath()); diff --git a/patches/server-unmapped/0001/0536-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch b/patches/server-unmapped/0001/0536-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch new file mode 100644 index 0000000000..3c64f53489 --- /dev/null +++ b/patches/server-unmapped/0001/0536-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 10 Jul 2020 12:38:12 -0500 +Subject: [PATCH] Fix SPIGOT-5885 Unable to disable advancements + + +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 3dcbeec94d86f3b56eaf05676b176f60d711f9cd..6818f8496ab76ee6ffc747bd6848b43830ec8914 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -132,6 +132,7 @@ public class Main { + return; + } + ++ 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; + File userCacheFile = new File("usercache.json"); diff --git a/patches/server-unmapped/0001/0537-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch b/patches/server-unmapped/0001/0537-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch new file mode 100644 index 0000000000..e8ab744e26 --- /dev/null +++ b/patches/server-unmapped/0001/0537-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 13 Jul 2020 06:22:54 -0700 +Subject: [PATCH] Fix AdvancementDataPlayer leak due from quitting early in + login + +Move the criterion storage to the AdvancementDataPlayer object +itself, so the criterion object stores no references - and thus +needs no cleanup. + +diff --git a/src/main/java/net/minecraft/advancements/critereon/CriterionTriggerAbstract.java b/src/main/java/net/minecraft/advancements/critereon/CriterionTriggerAbstract.java +index c2ee816af000f0c94782d704e6372cd93fd34bb3..c6388223daa26b200dc16cd562bcef19a5a59c8f 100644 +--- a/src/main/java/net/minecraft/advancements/critereon/CriterionTriggerAbstract.java ++++ b/src/main/java/net/minecraft/advancements/critereon/CriterionTriggerAbstract.java +@@ -16,25 +16,25 @@ import net.minecraft.world.level.storage.loot.LootTableInfo; + + public abstract class CriterionTriggerAbstract implements CriterionTrigger { + +- private final Map>> a = Maps.newIdentityHashMap(); ++ //private final Map>> a = Maps.newIdentityHashMap(); // Paper - moved into AdvancementDataPlayer to fix memory leak + + public CriterionTriggerAbstract() {} + + @Override + public final void a(AdvancementDataPlayer advancementdataplayer, CriterionTrigger.a criteriontrigger_a) { +- ((Set) this.a.computeIfAbsent(advancementdataplayer, (advancementdataplayer1) -> { ++ (advancementdataplayer.criterionData.computeIfAbsent(this, (advancementdataplayer1) -> { // Paper - fix AdvancementDataPlayer leak + return Sets.newHashSet(); + })).add(criteriontrigger_a); + } + + @Override + public final void b(AdvancementDataPlayer advancementdataplayer, CriterionTrigger.a criteriontrigger_a) { +- Set> set = (Set) this.a.get(advancementdataplayer); ++ Set> set = (Set) advancementdataplayer.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak + + if (set != null) { + set.remove(criteriontrigger_a); + if (set.isEmpty()) { +- this.a.remove(advancementdataplayer); ++ advancementdataplayer.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak + } + } + +@@ -42,7 +42,7 @@ public abstract class CriterionTriggerAbstract predicate) { + AdvancementDataPlayer advancementdataplayer = entityplayer.getAdvancementData(); +- Set> set = (Set) this.a.get(advancementdataplayer); ++ Set> set = (Set) advancementdataplayer.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak + + if (set != null && !set.isEmpty()) { + LootTableInfo loottableinfo = CriterionConditionEntity.b(entityplayer, entityplayer); +@@ -67,7 +67,7 @@ public abstract class CriterionTriggerAbstract> criterionData = Maps.newIdentityHashMap(); ++ // Paper end - fix advancement data player leakage ++ + public AdvancementDataPlayer(DataFixer datafixer, PlayerList playerlist, AdvancementDataWorld advancementdataworld, File file, EntityPlayer entityplayer) { + this.d = datafixer; + this.e = playerlist; diff --git a/patches/server-unmapped/0001/0538-Add-missing-strikeLighting-call-to-World-spigot-stri.patch b/patches/server-unmapped/0001/0538-Add-missing-strikeLighting-call-to-World-spigot-stri.patch new file mode 100644 index 0000000000..da0f3139d8 --- /dev/null +++ b/patches/server-unmapped/0001/0538-Add-missing-strikeLighting-call-to-World-spigot-stri.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 26 Jul 2020 12:11:39 +0100 +Subject: [PATCH] Add missing strikeLighting call to + World#spigot()#strikeLightningEffect + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 03761fd7367e184c808f4ef55bdef0838f7427b0..68445b7f32d2cf4c199666a732a36e02459a7b60 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2627,6 +2627,7 @@ public class CraftWorld implements World { + lightning.teleportAndSync( loc.getX(), loc.getY(), loc.getZ() ); + lightning.isEffect = true; + lightning.isSilent = isSilent; ++ world.strikeLightning( lightning ); + return (LightningStrike) lightning.getBukkitEntity(); + } + }; diff --git a/patches/server-unmapped/0001/0539-Fix-some-rails-connecting-improperly.patch b/patches/server-unmapped/0001/0539-Fix-some-rails-connecting-improperly.patch new file mode 100644 index 0000000000..9241cffb87 --- /dev/null +++ b/patches/server-unmapped/0001/0539-Fix-some-rails-connecting-improperly.patch @@ -0,0 +1,95 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 24 Jul 2020 15:56:05 -0700 +Subject: [PATCH] Fix some rails connecting improperly + + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockMinecartDetector.java b/src/main/java/net/minecraft/world/level/block/BlockMinecartDetector.java +index 61c4a57174237f5f77d841bb2274ace6297db8a4..27573a0334b06f4b6ada672057695d8f8a2bcfc4 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockMinecartDetector.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockMinecartDetector.java +@@ -71,6 +71,7 @@ public class BlockMinecartDetector extends BlockMinecartTrackAbstract { + + private void a(World world, BlockPosition blockposition, IBlockData iblockdata) { + if (this.canPlace(iblockdata, world, blockposition)) { ++ if (iblockdata.getBlock() != this) { return; } // Paper - not our block, don't do anything + boolean flag = (Boolean) iblockdata.get(BlockMinecartDetector.POWERED); + boolean flag1 = false; + List list = this.a(world, blockposition, EntityMinecartAbstract.class, (Predicate) null); +diff --git a/src/main/java/net/minecraft/world/level/block/BlockMinecartTrackAbstract.java b/src/main/java/net/minecraft/world/level/block/BlockMinecartTrackAbstract.java +index f65a53347f26affd1ce8d79527a6486e6bf8fbdd..0545dd68ea1f0a27739fac1a358c7def849e6b6a 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockMinecartTrackAbstract.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockMinecartTrackAbstract.java +@@ -62,6 +62,7 @@ public abstract class BlockMinecartTrackAbstract extends Block { + iblockdata = this.a(world, blockposition, iblockdata, true); + if (this.c) { + iblockdata.doPhysics(world, blockposition, this, blockposition, flag); ++ iblockdata = world.getType(blockposition); // Paper - don't desync, update again + } + + return iblockdata; +diff --git a/src/main/java/net/minecraft/world/level/block/MinecartTrackLogic.java b/src/main/java/net/minecraft/world/level/block/MinecartTrackLogic.java +index 8a8e3af0290a9483ee59d5fab061a9a9f5613f0a..436cfcde89aa780bcd58150f6167a365d8d955bc 100644 +--- a/src/main/java/net/minecraft/world/level/block/MinecartTrackLogic.java ++++ b/src/main/java/net/minecraft/world/level/block/MinecartTrackLogic.java +@@ -12,13 +12,19 @@ import net.minecraft.world.level.block.state.properties.BlockPropertyTrackPositi + + public class MinecartTrackLogic { + +- private final World a; +- private final BlockPosition b; ++ private final World a; public final World getWorld() { return this.a; } // Paper - OBFHELPER ++ private final BlockPosition b; public final BlockPosition getPos() { return this.b; } // Paper - OBFHELPER + private final BlockMinecartTrackAbstract c; +- private IBlockData d; ++ private IBlockData d; public final IBlockData getRailState() { return this.d; } // Paper - OBFHELPER + private final boolean e; + private final List f = Lists.newArrayList(); + ++ // Paper start - prevent desync ++ public boolean isValid() { ++ return this.getWorld().getType(this.getPos()).getBlock() == this.getRailState().getBlock(); ++ } ++ // Paper end - prevent desync ++ + public MinecartTrackLogic(World world, BlockPosition blockposition, IBlockData iblockdata) { + this.a = world; + this.b = blockposition; +@@ -153,6 +159,11 @@ public class MinecartTrackLogic { + } + + private void c(MinecartTrackLogic minecarttracklogic) { ++ // Paper start - prevent desync ++ if (!this.isValid() || !minecarttracklogic.isValid()) { ++ return; ++ } ++ // Paper end - prevent desync + this.f.add(minecarttracklogic.b); + BlockPosition blockposition = this.b.north(); + BlockPosition blockposition1 = this.b.south(); +@@ -347,11 +358,16 @@ public class MinecartTrackLogic { + this.d = (IBlockData) this.d.set(this.c.d(), blockpropertytrackposition1); + if (flag1 || this.a.getType(this.b) != this.d) { + this.a.setTypeAndData(this.b, this.d, 3); ++ // Paper start - prevent desync ++ if (!this.isValid()) { ++ return this; ++ } ++ // Paper end - prevent desync + + for (int i = 0; i < this.f.size(); ++i) { + MinecartTrackLogic minecarttracklogic = this.b((BlockPosition) this.f.get(i)); + +- if (minecarttracklogic != null) { ++ if (minecarttracklogic != null && minecarttracklogic.isValid()) { // Paper - prevent desync + minecarttracklogic.d(); + if (minecarttracklogic.b(this)) { + minecarttracklogic.c(this); +@@ -364,6 +380,6 @@ public class MinecartTrackLogic { + } + + public IBlockData c() { +- return this.d; ++ return this.getWorld().getType(this.getPos()); // Paper - prevent desync + } + } diff --git a/patches/server-unmapped/0001/0540-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch b/patches/server-unmapped/0001/0540-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch new file mode 100644 index 0000000000..f274992699 --- /dev/null +++ b/patches/server-unmapped/0001/0540-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch @@ -0,0 +1,134 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 4 Aug 2020 22:24:15 +0200 +Subject: [PATCH] Optimize Pathfinder - Remove Streams / Optimized collections + +I utilized the IDE to convert streams to non streams code, so shouldn't +be any risk of behavior change. Only did minor optimization of the +generated code set to remove unnecessary things. + +I expect us to just drop this patch on next major update and re-apply +it with the IDE again and re-apply the collections optimization. + +Optimize collection by creating a list instead of a set of the key and value. + +This lets us get faster foreach iteration, as well as avoids map lookups on +the values when needed. + +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java b/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java +index 5784be69098805e4d550a0923ac8daa5aada73f9..76e19f3a4ae988f6f3b59763d639fa5e084fa0bf 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/Pathfinder.java +@@ -33,9 +33,12 @@ public class Pathfinder { + this.d.a(); + this.c.a(chunkcache, entityinsentient); + PathPoint pathpoint = this.c.b(); +- Map map = (Map) set.stream().collect(Collectors.toMap((blockposition) -> { +- return this.c.a((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()); +- }, Function.identity())); ++ // Paper start - remove streams - and optimize collection ++ List> map = Lists.newArrayList(); ++ for (BlockPosition blockposition : set) { ++ map.add(new java.util.AbstractMap.SimpleEntry<>(this.c.a((double) blockposition.getX(), blockposition.getY(), blockposition.getZ()), blockposition)); ++ } ++ // Paper end + PathEntity pathentity = this.a(pathpoint, map, f, i, f1); + + this.c.a(); +@@ -43,17 +46,17 @@ public class Pathfinder { + } + + @Nullable +- private PathEntity a(PathPoint pathpoint, Map map, float f, int i, float f1) { +- Set set = map.keySet(); ++ private PathEntity a(PathPoint pathpoint, List> list, float f, int i, float f1) { // Paper - optimize collection ++ //Set set = map.keySet(); // Paper + + pathpoint.e = 0.0F; +- pathpoint.f = this.a(pathpoint, set); ++ pathpoint.f = this.a(pathpoint, list); // Paper - optimize collection + pathpoint.g = pathpoint.f; + this.d.a(); + this.d.a(pathpoint); + Set set1 = ImmutableSet.of(); + int j = 0; +- Set set2 = Sets.newHashSetWithExpectedSize(set.size()); ++ List> set2 = Lists.newArrayListWithExpectedSize(list.size()); // Paper - optimize collection + int k = (int) ((float) this.b * f1); + + while (!this.d.e()) { +@@ -65,14 +68,15 @@ public class Pathfinder { + PathPoint pathpoint1 = this.d.c(); + + pathpoint1.i = true; +- Iterator iterator = set.iterator(); +- +- while (iterator.hasNext()) { +- PathDestination pathdestination = (PathDestination) iterator.next(); ++ // Paper start - optimize collection ++ for (int i1 = 0; i1 < list.size(); i1++) { ++ Map.Entry entry = list.get(i1); ++ PathDestination pathdestination = entry.getKey(); + + if (pathpoint1.c((PathPoint) pathdestination) <= (float) i) { + pathdestination.e(); +- set2.add(pathdestination); ++ set2.add(entry); ++ // Paper end + } + } + +@@ -93,7 +97,7 @@ public class Pathfinder { + if (pathpoint2.j < f && (!pathpoint2.c() || f3 < pathpoint2.e)) { + pathpoint2.h = pathpoint1; + pathpoint2.e = f3; +- pathpoint2.f = this.a(pathpoint2, set) * 1.5F; ++ pathpoint2.f = this.a(pathpoint2, list) * 1.5F; // Paper - list instead of set + if (pathpoint2.c()) { + this.d.a(pathpoint2, pathpoint2.e + pathpoint2.f); + } else { +@@ -105,28 +109,29 @@ public class Pathfinder { + } + } + +- Optional optional = !set2.isEmpty() ? set2.stream().map((pathdestination1) -> { +- return this.a(pathdestination1.d(), (BlockPosition) map.get(pathdestination1), true); +- }).min(Comparator.comparingInt(PathEntity::e)) : set.stream().map((pathdestination1) -> { +- return this.a(pathdestination1.d(), (BlockPosition) map.get(pathdestination1), false); +- }).min(Comparator.comparingDouble(PathEntity::n).thenComparingInt(PathEntity::e)); +- +- if (!optional.isPresent()) { +- return null; +- } else { +- PathEntity pathentity = (PathEntity) optional.get(); +- +- return pathentity; ++ // Paper start - remove streams - and optimize collection ++ PathEntity best = null; ++ boolean useSet1 = set2.isEmpty(); ++ Comparator comparator = useSet1 ? Comparator.comparingInt(PathEntity::e) ++ : Comparator.comparingDouble(PathEntity::n).thenComparingInt(PathEntity::e); ++ for (Map.Entry entry : useSet1 ? list : set2) { ++ PathEntity pathEntity = this.a(entry.getKey().d(), entry.getValue(), !useSet1); ++ if (best == null || comparator.compare(pathEntity, best) < 0) ++ best = pathEntity; + } ++ return best; ++ // Paper end + } + +- private float a(PathPoint pathpoint, Set set) { ++ private float a(PathPoint pathpoint, List> list) { // Paper - optimize collection + float f = Float.MAX_VALUE; + + float f1; + +- for (Iterator iterator = set.iterator(); iterator.hasNext(); f = Math.min(f1, f)) { +- PathDestination pathdestination = (PathDestination) iterator.next(); ++ // Paper start - optimize collection ++ for (int i = 0, listSize = list.size(); i < listSize; f = Math.min(f1, f), i++) { // Paper ++ PathDestination pathdestination = list.get(i).getKey(); // Paper ++ // Paper end + + f1 = pathpoint.a(pathdestination); + pathdestination.a(f1, pathpoint); diff --git a/patches/server-unmapped/0001/0541-Incremental-player-saving.patch b/patches/server-unmapped/0001/0541-Incremental-player-saving.patch new file mode 100644 index 0000000000..cd0076d693 --- /dev/null +++ b/patches/server-unmapped/0001/0541-Incremental-player-saving.patch @@ -0,0 +1,95 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 9 Aug 2020 08:59:25 +0300 +Subject: [PATCH] Incremental player saving + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index b67ba8f75e4a3358d7c2462918b85b0bf9b5a922..fdbd8b89bb8bf3b61f60b812b90483c98a3d5ccb 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -440,4 +440,15 @@ public class PaperConfig { + allowPistonDuplication = getBoolean("settings.unsupported-settings.allow-piston-duplication", config.getBoolean("settings.unsupported-settings.allow-tnt-duplication", false)); + set("settings.unsupported-settings.allow-tnt-duplication", null); + } ++ ++ public static int playerAutoSaveRate = -1; ++ public static int maxPlayerAutoSavePerTick = 10; ++ private static void playerAutoSaveRate() { ++ playerAutoSaveRate = getInt("settings.player-auto-save-rate", -1); ++ maxPlayerAutoSavePerTick = getInt("settings.max-player-auto-save-per-tick", -1); ++ if (maxPlayerAutoSavePerTick == -1) { // -1 Automatic / "Recommended" ++ // 10 should be safe for everyone unless you mass spamming player auto save ++ maxPlayerAutoSavePerTick = (playerAutoSaveRate == -1 || playerAutoSaveRate > 100) ? 10 : 20; ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 50c1c6cc53eead25d03ed45427cb9fb80bc2fc36..483abe5b3c0c00ebdea405e9bb24509f743bfc2c 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1347,9 +1347,15 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit // Paper - move down + //MinecraftServer.LOGGER.debug("Autosave started"); // Paper + serverAutoSave = (autosavePeriod > 0 && this.ticks % autosavePeriod == 0); // Paper ++ // Paper start ++ int playerSaveInterval = com.destroystokyo.paper.PaperConfig.playerAutoSaveRate; ++ if (playerSaveInterval < 0) { ++ playerSaveInterval = autosavePeriod; ++ } ++ // Paper end + this.methodProfiler.enter("save"); +- if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // Paper +- this.playerList.savePlayers(); ++ if (playerSaveInterval > 0) { // Paper ++ this.playerList.savePlayers(playerSaveInterval); // Paper + }// Paper + // Paper start + for (WorldServer world : getWorlds()) { +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 21d1b4e0fe93b28cdadac043d081306d32032f81..1a08be6a490f0b94f86ee87ad06e60b66afc63f9 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -175,6 +175,7 @@ import org.bukkit.inventory.MainHand; + public class EntityPlayer extends EntityHuman implements ICrafting { + + private static final Logger LOGGER = LogManager.getLogger(); ++ public long lastSave = MinecraftServer.currentTick; // Paper + public PlayerConnection playerConnection; + public NetworkManager networkManager; // Paper + public final MinecraftServer server; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 1b78f53d09cc26fe881933df8aa1a7e77337acf1..939e3721af52069f1abad79d28c0555be3ac2d59 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -563,6 +563,7 @@ public abstract class PlayerList { + protected void savePlayerFile(EntityPlayer entityplayer) { + if (!entityplayer.getBukkitEntity().isPersistent()) return; // CraftBukkit + if (!entityplayer.didPlayerJoinEvent) return; // Paper - If we never fired PJE, we disconnected during login. Data has not changed, and additionally, our saved vehicle is not loaded! If we save now, we will lose our vehicle (CraftBukkit bug) ++ entityplayer.lastSave = MinecraftServer.currentTick; // Paper + this.playerFileData.save(entityplayer); + ServerStatisticManager serverstatisticmanager = (ServerStatisticManager) entityplayer.getStatisticManager(); // CraftBukkit + +@@ -1222,10 +1223,21 @@ public abstract class PlayerList { + } + + public void savePlayers() { ++ // Paper start - incremental player saving ++ savePlayers(null); ++ } ++ public void savePlayers(Integer interval) { + 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.savePlayerFile((EntityPlayer) this.players.get(i)); ++ EntityPlayer entityplayer = this.players.get(i); ++ if (interval == null || now - entityplayer.lastSave >= interval) { ++ this.savePlayerFile(entityplayer); ++ if (interval != null && ++numSaved <= com.destroystokyo.paper.PaperConfig.maxPlayerAutoSavePerTick) { break; } ++ } ++ // Paper end + } + MinecraftTimings.savePlayers.stopTiming(); // Paper + return null; }); // Paper - ensure main diff --git a/patches/server-unmapped/0001/0542-Import-fastutil-classes.patch b/patches/server-unmapped/0001/0542-Import-fastutil-classes.patch new file mode 100644 index 0000000000..a367d82509 --- /dev/null +++ b/patches/server-unmapped/0001/0542-Import-fastutil-classes.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 12 Aug 2020 11:33:04 +0200 +Subject: [PATCH] Import fastutil classes + + +diff --git a/src/main/java/net/minecraft/network/syncher/DataWatcher.java b/src/main/java/net/minecraft/network/syncher/DataWatcher.java +index 3cdae5409989ea4bad8311b0083517b7815caae1..bb09b7be91b488ebc336b2b6f5deafc0ccab27a4 100644 +--- a/src/main/java/net/minecraft/network/syncher/DataWatcher.java ++++ b/src/main/java/net/minecraft/network/syncher/DataWatcher.java +@@ -17,6 +17,7 @@ import net.minecraft.CrashReportSystemDetails; + import net.minecraft.ReportedException; + import net.minecraft.network.PacketDataSerializer; + import net.minecraft.world.entity.Entity; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; // Paper + import org.apache.commons.lang3.ObjectUtils; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; +@@ -26,7 +27,7 @@ public class DataWatcher { + private static final Logger LOGGER = LogManager.getLogger(); + private static final Map, Integer> b = Maps.newHashMap(); + private final Entity entity; +- private final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap> entries = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(); // Spigot - use better map // PAIL ++ private final Int2ObjectOpenHashMap> entries = new Int2ObjectOpenHashMap<>(); // Spigot - use better map // PAIL + // private final ReadWriteLock lock = new ReentrantReadWriteLock(); // Spigot - not required + private boolean f = true; + private boolean g; diff --git a/patches/server-unmapped/0001/0543-Don-t-mark-null-chunk-sections-for-block-updates.patch b/patches/server-unmapped/0001/0543-Don-t-mark-null-chunk-sections-for-block-updates.patch new file mode 100644 index 0000000000..c694cf5898 --- /dev/null +++ b/patches/server-unmapped/0001/0543-Don-t-mark-null-chunk-sections-for-block-updates.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Fri, 14 Aug 2020 23:41:19 +0200 +Subject: [PATCH] Don't mark null chunk sections for block updates + + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index d6a5a0b17308913a5efd97cd27fabd0825ef68c6..00cebd33101916f29bbc192d531ac0fba31e037b 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -450,6 +450,7 @@ public class PlayerChunk { + this.a(world, blockposition, iblockdata); + } else { + ChunkSection chunksection = chunk.getSections()[sectionposition.getY()]; ++ if (chunksection == null) chunksection = new ChunkSection(sectionposition.getY(), chunk, world, true); // Paper - make a new chunk section if none was found + PacketPlayOutMultiBlockChange packetplayoutmultiblockchange = new PacketPlayOutMultiBlockChange(sectionposition, shortset, chunksection, this.x); + + this.a(packetplayoutmultiblockchange, false); diff --git a/patches/server-unmapped/0001/0544-Remove-armour-stand-double-add-to-world.patch b/patches/server-unmapped/0001/0544-Remove-armour-stand-double-add-to-world.patch new file mode 100644 index 0000000000..835c6e761b --- /dev/null +++ b/patches/server-unmapped/0001/0544-Remove-armour-stand-double-add-to-world.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Fri, 14 Aug 2020 23:59:26 +0200 +Subject: [PATCH] Remove armour stand double add to world + + +diff --git a/src/main/java/net/minecraft/world/item/ItemArmorStand.java b/src/main/java/net/minecraft/world/item/ItemArmorStand.java +index dd0dc2a321f2f99cbf1060e47deec0f690d6b948..cd46df5485ebfd597ea72360a27872d46174ee19 100644 +--- a/src/main/java/net/minecraft/world/item/ItemArmorStand.java ++++ b/src/main/java/net/minecraft/world/item/ItemArmorStand.java +@@ -53,7 +53,7 @@ public class ItemArmorStand extends Item { + return EnumInteractionResult.FAIL; + } + +- worldserver.addAllEntities(entityarmorstand); ++ // Paper - moved down + float f = (float) MathHelper.d((MathHelper.g(itemactioncontext.h() - 180.0F) + 22.5F) / 45.0F) * 45.0F; + + entityarmorstand.setPositionRotation(entityarmorstand.locX(), entityarmorstand.locY(), entityarmorstand.locZ(), f, 0.0F); +@@ -63,7 +63,7 @@ public class ItemArmorStand extends Item { + return EnumInteractionResult.FAIL; + } + // CraftBukkit end +- world.addEntity(entityarmorstand); ++ worldserver.addAllEntities(entityarmorstand); // Paper - moved down + world.playSound((EntityHuman) null, entityarmorstand.locX(), entityarmorstand.locY(), entityarmorstand.locZ(), SoundEffects.ENTITY_ARMOR_STAND_PLACE, SoundCategory.BLOCKS, 0.75F, 0.8F); + } + diff --git a/patches/server-unmapped/0001/0545-Fix-MC-187716-Use-configured-height.patch b/patches/server-unmapped/0001/0545-Fix-MC-187716-Use-configured-height.patch new file mode 100644 index 0000000000..ba23e86875 --- /dev/null +++ b/patches/server-unmapped/0001/0545-Fix-MC-187716-Use-configured-height.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 15 Aug 2020 08:04:49 -0500 +Subject: [PATCH] Fix MC-187716 Use configured height + + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNether.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNether.java +index 9efe3a1dc5da760f0d8b0b39a10e642a53321aa5..ea2b107cddc7c9a6e8b8a0590e3b22a9cd7e34a6 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNether.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNether.java +@@ -36,7 +36,7 @@ public class WorldGenSurfaceNether extends WorldGenSurface= 0; --k2) { ++ for (int k2 = k; k2 >= 0; --k2) { // Paper - fix MC-187716 - use configured height + blockposition_mutableblockposition.d(k1, k2, l1); + IBlockData iblockdata4 = ichunkaccess.getType(blockposition_mutableblockposition); + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherAbstract.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherAbstract.java +index 22926296e66866c7fca13466004c20a16e94dc47..b9523f5611b5b8d786fddcc5fd265e8a2043ab6c 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherAbstract.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherAbstract.java +@@ -44,7 +44,7 @@ public abstract class WorldGenSurfaceNetherAbstract extends WorldGenSurface= 0; --k2) { ++ for (int k2 = k; k2 >= 0; --k2) { // Paper - fix MC-187716 - use configured height + blockposition_mutableblockposition.d(k1, k2, l1); + IBlockData iblockdata5 = ichunkaccess.getType(blockposition_mutableblockposition); + int l2; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherForest.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherForest.java +index 3bd78b0fc75a536e4e37f5ac67843ff778cd1b5f..a2abfc8816f2dad6c95aa89b443af0d3dec480aa 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherForest.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/WorldGenSurfaceNetherForest.java +@@ -34,7 +34,7 @@ public class WorldGenSurfaceNetherForest extends WorldGenSurface= 0; --k2) { ++ for (int k2 = k; k2 >= 0; --k2) { // Paper - fix MC-187716 - use configured height + blockposition_mutableblockposition.d(k1, k2, l1); + IBlockData iblockdata3 = worldgensurfaceconfigurationbase.a(); + IBlockData iblockdata4 = ichunkaccess.getType(blockposition_mutableblockposition); diff --git a/patches/server-unmapped/0001/0546-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch b/patches/server-unmapped/0001/0546-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch new file mode 100644 index 0000000000..fb98e08a74 --- /dev/null +++ b/patches/server-unmapped/0001/0546-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mbax +Date: Mon, 17 Aug 2020 12:17:37 -0400 +Subject: [PATCH] Fix regex mistake in CB NBT int deserialization + +The existing regex is too open and allows for the absence of any actual +number data, detecting an NBT entry of just the letter "i" in upper or +lower case. This causes a single-character NBT entry to be processed as +an integer ending in "i", passing an empty String to to Integer.parseInt, +triggering an exception in loading the item. + +This commit forces numbers to be present prior to the ending "i" +letter. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java b/src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java +index 1c5d62abcb968c2a60034e717aeb6d0df6548b92..83985a5aed4db54880c16228990abb3e495b079a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftNBTTagConfigSerializer.java +@@ -19,7 +19,7 @@ import net.minecraft.nbt.NBTTagString; + public class CraftNBTTagConfigSerializer { + + private static final Pattern ARRAY = Pattern.compile("^\\[.*]"); +- private static final Pattern INTEGER = Pattern.compile("[-+]?(?:0|[1-9][0-9]*)?i", Pattern.CASE_INSENSITIVE); ++ private static final Pattern INTEGER = Pattern.compile("[-+]?(?:0|[1-9][0-9]*)i", Pattern.CASE_INSENSITIVE); // Paper - fix regex + private static final Pattern DOUBLE = Pattern.compile("[-+]?(?:[0-9]+[.]?|[0-9]*[.][0-9]+)(?:e[-+]?[0-9]+)?d", Pattern.CASE_INSENSITIVE); + private static final MojangsonParser MOJANGSON_PARSER = new MojangsonParser(new StringReader("")); + diff --git a/patches/server-unmapped/0001/0547-Do-not-let-the-server-load-chunks-from-newer-version.patch b/patches/server-unmapped/0001/0547-Do-not-let-the-server-load-chunks-from-newer-version.patch new file mode 100644 index 0000000000..1848cd99d4 --- /dev/null +++ b/patches/server-unmapped/0001/0547-Do-not-let-the-server-load-chunks-from-newer-version.patch @@ -0,0 +1,39 @@ +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/ChunkRegionLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +index 8e4924cd649c350520cba54a0e1497d5acf089ff..2e5221bc1b9e260e33f2cef2653dc59d05e2680d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +@@ -95,10 +95,24 @@ public class ChunkRegionLoader { + return holder.protoChunk; + } + ++ // Paper start ++ private static final int CURRENT_DATA_VERSION = SharedConstants.getGameVersion().getWorldVersion(); ++ private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); ++ // Paper end ++ + public static InProgressChunkHolder loadChunk(WorldServer worldserver, DefinedStructureManager definedstructuremanager, VillagePlace villageplace, ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound, boolean distinguish) { + ArrayDeque tasksToExecuteOnMain = new ArrayDeque<>(); + // Paper end + ChunkGenerator chunkgenerator = worldserver.getChunkProvider().getChunkGenerator(); ++ // Paper start - Do NOT attempt to load chunks saved with newer versions ++ if (nbttagcompound.hasKeyOfType("DataVersion", 99)) { ++ int dataVersion = nbttagcompound.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 + WorldChunkManager worldchunkmanager = chunkgenerator.getWorldChunkManager(); + NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level"); // Paper - diff on change, see ChunkRegionLoader#getChunkCoordinate + ChunkCoordIntPair chunkcoordintpair1 = new ChunkCoordIntPair(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); // Paper - diff on change, see ChunkRegionLoader#getChunkCoordinate diff --git a/patches/server-unmapped/0001/0548-Brand-support.patch b/patches/server-unmapped/0001/0548-Brand-support.patch new file mode 100644 index 0000000000..80ab9fd4d4 --- /dev/null +++ b/patches/server-unmapped/0001/0548-Brand-support.patch @@ -0,0 +1,92 @@ +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/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 92d5a58f7d18aebf06e3cb868abf3f41825f2f84..1d154ef60fd979340cf925748251669e860d4094 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -5,6 +5,7 @@ import com.google.common.primitives.Doubles; + import com.google.common.primitives.Floats; + import com.mojang.brigadier.ParseResults; + import com.mojang.brigadier.StringReader; ++import io.netty.buffer.Unpooled; + import io.netty.util.concurrent.Future; + import io.netty.util.concurrent.GenericFutureListener; + import it.unimi.dsi.fastutil.ints.Int2ShortMap; +@@ -37,6 +38,7 @@ import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTTagList; + import net.minecraft.nbt.NBTTagString; + import net.minecraft.network.NetworkManager; ++import net.minecraft.network.PacketDataSerializer; + import net.minecraft.network.chat.ChatComponentText; + import net.minecraft.network.chat.ChatMessage; + import net.minecraft.network.chat.ChatMessageType; +@@ -260,6 +262,8 @@ public class PlayerConnection implements PacketListenerPlayIn { + private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); + private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit + ++ private String clientBrandName = null; // Paper - Brand name ++ + public PlayerConnection(MinecraftServer minecraftserver, NetworkManager networkmanager, EntityPlayer entityplayer) { + this.minecraftServer = minecraftserver; + this.networkManager = networkmanager; +@@ -2999,6 +3003,8 @@ public class PlayerConnection implements PacketListenerPlayIn { + private static final MinecraftKey CUSTOM_REGISTER = new MinecraftKey("register"); + private static final MinecraftKey CUSTOM_UNREGISTER = new MinecraftKey("unregister"); + ++ private static final MinecraftKey MINECRAFT_BRAND = new MinecraftKey("brand"); // Paper - Brand support ++ + @Override + public void a(PacketPlayInCustomPayload packetplayincustompayload) { + PlayerConnectionUtils.ensureMainThread(packetplayincustompayload, this, this.player.getWorldServer()); +@@ -3026,6 +3032,16 @@ public class PlayerConnection implements PacketListenerPlayIn { + try { + byte[] data = new byte[packetplayincustompayload.data.readableBytes()]; + packetplayincustompayload.data.readBytes(data); ++ ++ // Paper start - Brand support ++ if (packetplayincustompayload.tag.equals(MINECRAFT_BRAND)) { ++ try { ++ this.clientBrandName = new PacketDataSerializer(Unpooled.copiedBuffer(data)).readUTF(256); ++ } catch (StringIndexOutOfBoundsException ex) { ++ this.clientBrandName = "illegal"; ++ } ++ } ++ // Paper end + server.getMessenger().dispatchIncomingMessage(player.getBukkitEntity(), packetplayincustompayload.tag.toString(), data); + } catch (Exception ex) { + PlayerConnection.LOGGER.error("Couldn\'t dispatch custom payload", ex); +@@ -3035,6 +3051,12 @@ public class PlayerConnection implements PacketListenerPlayIn { + + } + ++ // Paper start - brand support ++ public String getClientBrandName() { ++ return clientBrandName; ++ } ++ // Paper end ++ + public final boolean isDisconnected() { + return (!this.player.joining && !this.networkManager.isConnected()) || this.processedDisconnect; // Paper + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index ba8c36c9fa7a1fdc3047b14042da8703802f9275..a4726747c066d623f37162e2637403efee7f5708 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2385,6 +2385,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // Paper end + }; + ++ // Paper start - brand support ++ @Override ++ public String getClientBrandName() { ++ return getHandle().playerConnection != null ? getHandle().playerConnection.getClientBrandName() : null; ++ } ++ // Paper end ++ + public Player.Spigot spigot() + { + return spigot; diff --git a/patches/server-unmapped/0001/0549-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch b/patches/server-unmapped/0001/0549-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch new file mode 100644 index 0000000000..61129f1906 --- /dev/null +++ b/patches/server-unmapped/0001/0549-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Thu, 20 Aug 2020 19:24:13 -0700 +Subject: [PATCH] Fix MC-99259 Wither Boss Bar doesn't update until + invulnerability period is over + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java +index 174eb12722872182b2d9b54841e5bb57893695a1..30290c0208a4725b2eb0e7764465c354e592e4ee 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java +@@ -393,8 +393,9 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit + } + +- this.bossBattle.setProgress(this.getHealth() / this.getMaxHealth()); ++ //this.bossBattle.setProgress(this.getHealth() / this.getMaxHealth()); // Paper - Moved down + } ++ this.bossBattle.setProgress(this.getHealth() / this.getMaxHealth()); // Paper - Fix MC-99259 (Boss bar does not update until Wither invulnerability period ends) + } + + public static boolean c(IBlockData iblockdata) { diff --git a/patches/server-unmapped/0001/0550-Fix-MC-197271.patch b/patches/server-unmapped/0001/0550-Fix-MC-197271.patch new file mode 100644 index 0000000000..5c61d89c86 --- /dev/null +++ b/patches/server-unmapped/0001/0550-Fix-MC-197271.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Sun, 23 Aug 2020 10:57:44 +0200 +Subject: [PATCH] Fix MC-197271 + +This patch only fixes an issue for servers running OpenJ9. + +diff --git a/src/main/java/net/minecraft/data/RegistryGeneration.java b/src/main/java/net/minecraft/data/RegistryGeneration.java +index 25762daca063ff5a422975d7fe64752a2deae163..2c06e3ab167443ac54c27cfe09e279f18a9f7300 100644 +--- a/src/main/java/net/minecraft/data/RegistryGeneration.java ++++ b/src/main/java/net/minecraft/data/RegistryGeneration.java +@@ -48,11 +48,11 @@ public class RegistryGeneration { + public static final IRegistry g = a(IRegistry.aw, () -> { + return ProcessorLists.b; + }); +- public static final IRegistry h = a(IRegistry.ax, WorldGenFeaturePieces::a); ++ public static final IRegistry h = a(IRegistry.ax, () -> WorldGenFeaturePieces.a()); // Paper - MC-197271 + public static final IRegistry WORLDGEN_BIOME = a(IRegistry.ay, () -> { + return BiomeRegistry.a; + }); +- public static final IRegistry j = a(IRegistry.ar, GeneratorSettingBase::i); ++ public static final IRegistry j = a(IRegistry.ar, () -> GeneratorSettingBase.i()); // Paper - MC-197271 + + private static IRegistry a(ResourceKey> resourcekey, Supplier supplier) { + return a(resourcekey, Lifecycle.stable(), supplier); +@@ -66,9 +66,9 @@ public class RegistryGeneration { + MinecraftKey minecraftkey = resourcekey.a(); + + RegistryGeneration.k.put(minecraftkey, supplier); +- IRegistryWritable iregistrywritable = RegistryGeneration.l; ++ IRegistryWritable iregistrywritable = (IRegistryWritable) RegistryGeneration.l; // Paper - decompile fix + +- return (IRegistryWritable) iregistrywritable.a(resourcekey, (Object) r0, lifecycle); ++ return (R) iregistrywritable.a((ResourceKey) resourcekey, r0, lifecycle); // Paper - decompile fix + } + + public static T a(IRegistry iregistry, String s, T t0) { +@@ -76,11 +76,11 @@ public class RegistryGeneration { + } + + public static T a(IRegistry iregistry, MinecraftKey minecraftkey, T t0) { +- return ((IRegistryWritable) iregistry).a(ResourceKey.a(iregistry.f(), minecraftkey), t0, Lifecycle.stable()); ++ return (T) ((IRegistryWritable) iregistry).a(ResourceKey.a(iregistry.f(), minecraftkey), t0, Lifecycle.stable()); // Paper - decompile fix + } + + public static T a(IRegistry iregistry, int i, ResourceKey resourcekey, T t0) { +- return ((IRegistryWritable) iregistry).a(i, resourcekey, t0, Lifecycle.stable()); ++ return (T) ((IRegistryWritable) iregistry).a(i, resourcekey, t0, Lifecycle.stable()); // Paper - decompile fix + } + + public static void a() {} diff --git a/patches/server-unmapped/0001/0551-MC-197883-Bandaid-decode-issue.patch b/patches/server-unmapped/0001/0551-MC-197883-Bandaid-decode-issue.patch new file mode 100644 index 0000000000..fd35ea34e4 --- /dev/null +++ b/patches/server-unmapped/0001/0551-MC-197883-Bandaid-decode-issue.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 21 Aug 2020 21:05:28 -0400 +Subject: [PATCH] MC-197883: Bandaid decode issue + +Mojang has a mix of type and name in the data sets, but you can only +use one. + +This will retry as name if type is asked for and not found. + +diff --git a/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java b/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java +index de7d1e5e0319c65775d932144c268c2d55bb7dc7..bd6a0e1b5454e880a4f2a16be7dc8da64b73e11d 100644 +--- a/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java ++++ b/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java +@@ -48,7 +48,12 @@ public class KeyDispatchCodec extends MapCodec { + + @Override + public DataResult decode(final DynamicOps ops, final MapLike input) { +- final T elementName = input.get(typeKey); ++ // Paper start - bandaid MC-197883 ++ T elementName = input.get(typeKey); ++ if (elementName == null && "type".equals(typeKey)) { ++ elementName = input.get("name"); ++ } ++ // Paper end + if (elementName == null) { + return DataResult.error("Input does not contain a key [" + typeKey + "]: " + input); + } diff --git a/patches/server-unmapped/0001/0552-Add-setMaxPlayers-API.patch b/patches/server-unmapped/0001/0552-Add-setMaxPlayers-API.patch new file mode 100644 index 0000000000..3d97a9da0a --- /dev/null +++ b/patches/server-unmapped/0001/0552-Add-setMaxPlayers-API.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 22 Aug 2020 23:59:30 +0200 +Subject: [PATCH] Add #setMaxPlayers API + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 939e3721af52069f1abad79d28c0555be3ac2d59..272a899c4d599a97201f1c11d829abe40101377d 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -145,7 +145,7 @@ public abstract class PlayerList { + public final WorldNBTStorage playerFileData; + private boolean hasWhitelist; + private final IRegistryCustom.Dimension s; +- protected final int maxPlayers; ++ protected int maxPlayers; public final void setMaxPlayers(int maxPlayers) { this.maxPlayers = maxPlayers; } // Paper - remove final and add setter + private int viewDistance; + private EnumGamemode u; + private boolean v; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f8d040ba6c8b3a1294a8ac8d55cbd09705d4be76..1199644c6342379df21840aa144e731e9e9245ca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -618,6 +618,13 @@ public final class CraftServer implements Server { + return playerList.getMaxPlayers(); + } + ++ // Paper start ++ @Override ++ public void setMaxPlayers(int maxPlayers) { ++ this.playerList.setMaxPlayers(maxPlayers); ++ } ++ // Paper end ++ + // NOTE: These are dependent on the corresponding call in MinecraftServer + // so if that changes this will need to as well + @Override diff --git a/patches/server-unmapped/0001/0553-Add-playPickupItemAnimation-to-LivingEntity.patch b/patches/server-unmapped/0001/0553-Add-playPickupItemAnimation-to-LivingEntity.patch new file mode 100644 index 0000000000..22fffc954c --- /dev/null +++ b/patches/server-unmapped/0001/0553-Add-playPickupItemAnimation-to-LivingEntity.patch @@ -0,0 +1,20 @@ +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 5563e7c1ecc9e607ba0be21ae16a544b24d6f030..090e0931df410526cb7b0aab196a01f57ffbb285 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -814,5 +814,9 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + } + ++ @Override ++ public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { ++ getHandle().receive(((CraftItem) item).getHandle(), quantity); ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0554-Don-t-require-FACING-data.patch b/patches/server-unmapped/0001/0554-Don-t-require-FACING-data.patch new file mode 100644 index 0000000000..00f9b23e6f --- /dev/null +++ b/patches/server-unmapped/0001/0554-Don-t-require-FACING-data.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sun, 23 Aug 2020 19:01:04 +0200 +Subject: [PATCH] Don't require FACING data + + +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseBehaviorItem.java b/src/main/java/net/minecraft/core/dispenser/DispenseBehaviorItem.java +index 979b862284ecd0dc504ee8a7018b27ff569b7b85..3d14ec72bb9babdf6b24aca3bde06d8b54815f9d 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseBehaviorItem.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseBehaviorItem.java +@@ -15,20 +15,22 @@ import org.bukkit.event.block.BlockDispenseEvent; + // CraftBukkit end + + public class DispenseBehaviorItem implements IDispenseBehavior { ++ private EnumDirection enumdirection; // Paper + + public DispenseBehaviorItem() {} + + @Override + public final ItemStack dispense(ISourceBlock isourceblock, ItemStack itemstack) { ++ enumdirection = isourceblock.getBlockData().get(BlockDispenser.FACING); // Paper - cache facing direction + ItemStack itemstack1 = this.a(isourceblock, itemstack); + + this.a(isourceblock); +- this.a(isourceblock, (EnumDirection) isourceblock.getBlockData().get(BlockDispenser.FACING)); ++ this.a(isourceblock, enumdirection); // Paper - cache facing direction + return itemstack1; + } + + protected ItemStack a(ISourceBlock isourceblock, ItemStack itemstack) { +- EnumDirection enumdirection = (EnumDirection) isourceblock.getBlockData().get(BlockDispenser.FACING); ++ // Paper - cached enum direction + IPosition iposition = BlockDispenser.a(isourceblock); + ItemStack itemstack1 = itemstack.cloneAndSubtract(1); + diff --git a/patches/server-unmapped/0001/0555-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch b/patches/server-unmapped/0001/0555-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch new file mode 100644 index 0000000000..726d7dc075 --- /dev/null +++ b/patches/server-unmapped/0001/0555-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch @@ -0,0 +1,46 @@ +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/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 1a7d06d8a3d1fe0a2a943eae5efd23d28fe4bd62..246708808cbcd9a5c1b8690c869a514d02fddb5c 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1988,12 +1988,14 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + // Paper end + ++ public final void setSpawn(BlockPosition blockposition, float f) { this.a(blockposition, f); } // Paper - OBFHELPER + public void a(BlockPosition blockposition, float f) { + // Paper - configurable spawn radius + BlockPosition prevSpawn = this.getSpawn(); + //ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(new BlockPosition(this.worldData.a(), 0, this.worldData.c())); + + this.worldData.setSpawn(blockposition, f); ++ new org.bukkit.event.world.SpawnChangeEvent(getWorld(), MCUtil.toLocation(this, prevSpawn)).callEvent(); // Paper + if (this.keepSpawnInMemory) { + // if this keepSpawnInMemory is false a plugin has already removed our tickets, do not re-add + this.removeTicketsForSpawn(this.paperConfig.keepLoadedRange, prevSpawn); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 68445b7f32d2cf4c199666a732a36e02459a7b60..487e8547d7b50a3500267225b509582f241a26ec 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -389,11 +389,13 @@ public class CraftWorld implements World { + public boolean setSpawnLocation(int x, int y, int z, float angle) { + try { + Location previousLocation = getSpawnLocation(); +- world.worldData.setSpawn(new BlockPosition(x, y, z), angle); ++ world.setSpawn(new BlockPosition(x, y, z), angle); // Paper - use WorldServer#setSpawn + ++ // Paper start - move to nms.World + // Notify anyone who's listening. +- SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); +- server.getPluginManager().callEvent(event); ++ // SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); ++ // server.getPluginManager().callEvent(event); ++ // Paper end + + return true; + } catch (Exception e) { diff --git a/patches/server-unmapped/0001/0556-Add-moon-phase-API.patch b/patches/server-unmapped/0001/0556-Add-moon-phase-API.patch new file mode 100644 index 0000000000..06173c2468 --- /dev/null +++ b/patches/server-unmapped/0001/0556-Add-moon-phase-API.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 23 Aug 2020 16:32:11 +0200 +Subject: [PATCH] Add moon phase API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 487e8547d7b50a3500267225b509582f241a26ec..a824350ff95d6193c0c10e21ba8c3407c7a44dd1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -328,6 +328,11 @@ public class CraftWorld implements World { + public int getPlayerCount() { + return world.players.size(); + } ++ ++ @Override ++ public io.papermc.paper.world.MoonPhase getMoonPhase() { ++ return io.papermc.paper.world.MoonPhase.getPhase(getFullTime() / 24000L); ++ } + // Paper end + + private static final Random rand = new Random(); diff --git a/patches/server-unmapped/0001/0557-Prevent-headless-pistons-from-being-created.patch b/patches/server-unmapped/0001/0557-Prevent-headless-pistons-from-being-created.patch new file mode 100644 index 0000000000..f6e166986f --- /dev/null +++ b/patches/server-unmapped/0001/0557-Prevent-headless-pistons-from-being-created.patch @@ -0,0 +1,74 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index fdbd8b89bb8bf3b61f60b812b90483c98a3d5ccb..faa1b775e45563b93ac1d5b904938b1f5ad8d80c 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -441,6 +441,12 @@ public class PaperConfig { + set("settings.unsupported-settings.allow-tnt-duplication", null); + } + ++ public static boolean allowHeadlessPistons; ++ private static void allowHeadlessPistons() { ++ config.set("settings.unsupported-settings.allow-headless-pistons-readme", "This setting controls if players should be able to create headless pistons."); ++ allowHeadlessPistons = getBoolean("settings.unsupported-settings.allow-headless-pistons", false); ++ } ++ + public static int playerAutoSaveRate = -1; + public static int maxPlayerAutoSavePerTick = 10; + private static void playerAutoSaveRate() { +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 0b3479aae8f7cad7bd0b8b64aa2dead43baf4c56..79008bda42558ea7d28ccf51b66405a3bdb52da7 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -16,6 +16,7 @@ import java.util.Set; + import javax.annotation.Nullable; + import net.minecraft.core.BaseBlockPosition; + import net.minecraft.core.BlockPosition; ++import net.minecraft.core.EnumDirection; + import net.minecraft.core.particles.Particles; + import net.minecraft.server.level.WorldServer; + import net.minecraft.sounds.SoundCategory; +@@ -34,6 +35,8 @@ import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.BlockFireAbstract; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.entity.TileEntity; ++import net.minecraft.world.level.block.piston.BlockPistonExtension; ++import net.minecraft.world.level.block.piston.TileEntityPiston; + import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.level.material.Fluid; + import net.minecraft.world.level.storage.loot.LootTableInfo; +@@ -163,6 +166,15 @@ public class Explosion { + + if (f > 0.0F && this.l.a(this, this.world, blockposition, iblockdata, f) && blockposition.getY() < 256 && blockposition.getY() >= 0) { // CraftBukkit - don't wrap explosions + set.add(blockposition); ++ // Paper start - prevent headless pistons from forming ++ if (!com.destroystokyo.paper.PaperConfig.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { ++ TileEntity extension = this.world.getTileEntity(blockposition); ++ if (extension instanceof TileEntityPiston && ((TileEntityPiston) extension).isHead()) { ++ EnumDirection direction = iblockdata.get(BlockPistonExtension.FACING); ++ set.add(blockposition.shift(direction.opposite())); ++ } ++ } ++ // Paper end + } + + d4 += d0 * 0.30000001192092896D; +diff --git a/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java b/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java +index e70c3a8c9075b6c0bc73e6488d784dfe3b86e58d..58c7a52612fe0f5c1e4ddacc0bf93cd81f1286b8 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/TileEntityPiston.java +@@ -65,6 +65,8 @@ public class TileEntityPiston extends TileEntity implements ITickable { + return this.b; + } + ++ public final boolean isHead() { return this.h(); } // Paper - OBFHELPER ++ + public boolean h() { + return this.g; + } diff --git a/patches/server-unmapped/0001/0558-Add-BellRingEvent.patch b/patches/server-unmapped/0001/0558-Add-BellRingEvent.patch new file mode 100644 index 0000000000..6352b44128 --- /dev/null +++ b/patches/server-unmapped/0001/0558-Add-BellRingEvent.patch @@ -0,0 +1,53 @@ +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/net/minecraft/world/level/block/BlockBell.java b/src/main/java/net/minecraft/world/level/block/BlockBell.java +index 687f7acd8254294b568c9adf3e72d02d12551381..24754057bc83577f4854262bbb82db355591270e 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockBell.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockBell.java +@@ -1,8 +1,11 @@ + package net.minecraft.world.level.block; + ++import io.papermc.paper.event.block.BellRingEvent; ++ + import javax.annotation.Nullable; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundCategory; + import net.minecraft.sounds.SoundEffects; + import net.minecraft.stats.StatisticList; +@@ -89,7 +92,7 @@ public class BlockBell extends BlockTileEntity { + boolean flag1 = !flag || this.a(iblockdata, enumdirection, movingobjectpositionblock.getPos().y - (double) blockposition.getY()); + + if (flag1) { +- boolean flag2 = this.a(world, blockposition, enumdirection); ++ boolean flag2 = this.handleBellRing(world, blockposition, enumdirection, entityhuman); // Paper + + if (flag2 && entityhuman != null) { + entityhuman.a(StatisticList.BELL_RING); +@@ -123,6 +126,11 @@ public class BlockBell extends BlockTileEntity { + } + + public boolean a(World world, BlockPosition blockposition, @Nullable EnumDirection enumdirection) { ++ // Paper start - add ringer param ++ return this.handleBellRing(world, blockposition, enumdirection, null); ++ } ++ public boolean handleBellRing(World world, BlockPosition blockposition, @Nullable EnumDirection enumdirection, @Nullable Entity ringer) { ++ // Paper end + TileEntity tileentity = world.getTileEntity(blockposition); + + if (!world.isClientSide && tileentity instanceof TileEntityBell) { +@@ -130,6 +138,7 @@ public class BlockBell extends BlockTileEntity { + enumdirection = (EnumDirection) world.getType(blockposition).get(BlockBell.a); + } + ++ if (!new BellRingEvent(world.getWorld().getBlockAt(MCUtil.toLocation(world, blockposition)), ringer == null ? null : ringer.getBukkitEntity()).callEvent()) return false; // Paper - BellRingEvent + ((TileEntityBell) tileentity).a(enumdirection); + world.playSound((EntityHuman) null, blockposition, SoundEffects.BLOCK_BELL_USE, SoundCategory.BLOCKS, 2.0F, 1.0F); + return true; diff --git a/patches/server-unmapped/0001/0559-Add-zombie-targets-turtle-egg-config.patch b/patches/server-unmapped/0001/0559-Add-zombie-targets-turtle-egg-config.patch new file mode 100644 index 0000000000..2d5c7f12aa --- /dev/null +++ b/patches/server-unmapped/0001/0559-Add-zombie-targets-turtle-egg-config.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 23 Aug 2020 15:47:34 +0200 +Subject: [PATCH] Add zombie targets turtle egg config + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 978062774c1db286bfb9b0ffdef19d880b1f249b..36ecdfce84141ac731b827e469ac842f5c666259 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -668,4 +668,9 @@ public class PaperWorldConfig { + maxLightningFlashDistance = 512; // Vanilla value + } + } ++ ++ public boolean zombiesTargetTurtleEggs = true; ++ private void zombiesTargetTurtleEggs() { ++ zombiesTargetTurtleEggs = getBoolean("zombies-target-turtle-eggs", zombiesTargetTurtleEggs); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +index 3d8d4a43e6cd554b6f1eeafa1c8d43cef877139a..87acbdee03edf8bc35f4b3bcb9b82855ed4a3c33 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +@@ -107,7 +107,7 @@ public class EntityZombie extends EntityMonster { + + @Override + protected void initPathfinder() { +- this.goalSelector.a(4, new EntityZombie.a(this, 1.0D, 3)); ++ if (world.paperConfig.zombiesTargetTurtleEggs) this.goalSelector.a(4, new EntityZombie.a(this, 1.0D, 3)); // Paper + this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); + this.m(); diff --git a/patches/server-unmapped/0001/0560-Buffer-joins-to-world.patch b/patches/server-unmapped/0001/0560-Buffer-joins-to-world.patch new file mode 100644 index 0000000000..c45f7273b3 --- /dev/null +++ b/patches/server-unmapped/0001/0560-Buffer-joins-to-world.patch @@ -0,0 +1,71 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index faa1b775e45563b93ac1d5b904938b1f5ad8d80c..545948f20efd6c8dd42140b565af94cd6b52b661 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -457,4 +457,9 @@ public class PaperConfig { + maxPlayerAutoSavePerTick = (playerAutoSaveRate == -1 || playerAutoSaveRate > 100) ? 10 : 20; + } + } ++ ++ public static int maxJoinsPerTick; ++ private static void maxJoinsPerTick() { ++ maxJoinsPerTick = getInt("settings.max-joins-per-tick", 3); ++ } + } +diff --git a/src/main/java/net/minecraft/network/NetworkManager.java b/src/main/java/net/minecraft/network/NetworkManager.java +index ab70eeaeca222de7de7cab1b3db14b2c4761c3c3..878f879f8d410c428ad8a4c49e0c86c559bc47a9 100644 +--- a/src/main/java/net/minecraft/network/NetworkManager.java ++++ b/src/main/java/net/minecraft/network/NetworkManager.java +@@ -33,6 +33,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutTabComplete; + import net.minecraft.network.protocol.game.PacketPlayOutTitle; + import net.minecraft.server.CancelledPacketHandleException; + import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.network.LoginListener; + import net.minecraft.server.network.PlayerConnection; +@@ -382,10 +383,22 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + } + // Paper end + ++ private static final int MAX_PER_TICK = com.destroystokyo.paper.PaperConfig.maxJoinsPerTick; // Paper ++ private static int joinAttemptsThisTick; // Paper ++ private static int currTick; // Paper + public void a() { + this.p(); ++ // Paper start ++ if (currTick != MinecraftServer.currentTick) { ++ currTick = MinecraftServer.currentTick; ++ joinAttemptsThisTick = 0; ++ } ++ // Paper end + if (this.packetListener instanceof LoginListener) { ++ if ( ((LoginListener) this.packetListener).getLoginState() != LoginListener.EnumProtocolState.READY_TO_ACCEPT // Paper ++ || (joinAttemptsThisTick++ < MAX_PER_TICK)) { // Paper - limit the number of joins which can be processed each tick + ((LoginListener) this.packetListener).tick(); ++ } // Paper + } + + if (this.packetListener instanceof PlayerConnection) { +diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java +index 78a8eb89e9113a1002ba6177f96d5734a10e8d7d..39aa50a386d0ab94914cd8f85c69cf404e0dc4b2 100644 +--- a/src/main/java/net/minecraft/server/network/LoginListener.java ++++ b/src/main/java/net/minecraft/server/network/LoginListener.java +@@ -416,7 +416,7 @@ public class LoginListener implements PacketLoginInListener { + return new GameProfile(uuid, gameprofile.getName()); + } + +- static enum EnumProtocolState { ++ public enum EnumProtocolState { // Paper - package private -> public + + HELLO, KEY, AUTHENTICATING, NEGOTIATING, READY_TO_ACCEPT, DELAY_ACCEPT, ACCEPTED; + diff --git a/patches/server-unmapped/0001/0561-Optimize-redstone-algorithm.patch b/patches/server-unmapped/0001/0561-Optimize-redstone-algorithm.patch new file mode 100644 index 0000000000..4e538c9eb3 --- /dev/null +++ b/patches/server-unmapped/0001/0561-Optimize-redstone-algorithm.patch @@ -0,0 +1,1158 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: theosib +Date: Thu, 27 Sep 2018 01:43:35 -0600 +Subject: [PATCH] Optimize redstone algorithm + +Author: theosib +Co-authored-by: egg82 + +Original license: MIT + +This patch implements theosib's redstone algorithms to completely overhaul the way redstone works. +The new algorithms should be many times faster than current vanilla ones. +From the original author's comments, it looks like it shouldn't interfere with any redstone save for very extreme edge-cases. + +Surprisingly, not a lot was touched aside from a few obfuscation helpers and BlockRedstoneWire. +A lot of this code is self-contained in a helper class. + +Aside from making the obvious class/function renames and obfhelpers I didn't need to modify much. +Just added Bukkit's event system and took a few liberties with dead code and comment misspellings. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 36ecdfce84141ac731b827e469ac842f5c666259..02bb85364560784adea47c877c13291c3d016b86 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -673,4 +673,14 @@ public class PaperWorldConfig { + private void zombiesTargetTurtleEggs() { + zombiesTargetTurtleEggs = getBoolean("zombies-target-turtle-eggs", zombiesTargetTurtleEggs); + } ++ ++ public boolean useEigencraftRedstone = false; ++ private void useEigencraftRedstone() { ++ useEigencraftRedstone = this.getBoolean("use-faster-eigencraft-redstone", false); ++ if (useEigencraftRedstone) { ++ log("Using Eigencraft redstone algorithm by theosib."); ++ } else { ++ log("Using vanilla redstone algorithm."); ++ } ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..167e0aaec8c2f83856465b7efc9ac9e5f9389d91 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java +@@ -0,0 +1,914 @@ ++package com.destroystokyo.paper.util; ++ ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.ThreadLocalRandom; ++ ++import net.minecraft.core.BlockPosition; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.level.World; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.BlockRedstoneWire; ++import net.minecraft.world.level.block.state.IBlockData; ++import org.bukkit.event.block.BlockRedstoneEvent; ++ ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++ ++/** ++ * Used for the faster redstone algorithm. ++ * Original author: theosib ++ * Original license: MIT ++ * ++ * Ported to Paper and updated to 1.13 by egg82 ++ */ ++public class RedstoneWireTurbo { ++ /* ++ * This is Helper class for BlockRedstoneWire. It implements a minimally-invasive ++ * bolt-on accelerator that performs a breadth-first search through redstone wire blocks ++ * in order to more efficiently and deterministically compute new redstone wire power levels ++ * and determine the order in which other blocks should be updated. ++ * ++ * Features: ++ * - Changes to BlockRedstoneWire are very limited, no other classes are affected, and the ++ * choice between old and new redstone wire update algorithms is switchable on-line. ++ * - The vanilla implementation relied on World.notifyNeighborsOfStateChange for redstone ++ * wire blocks to communicate power level changes to each other, generating 36 block ++ * updates per call. This improved implementation propagates power level changes directly ++ * between redstone wire blocks. Redstone wire power levels are therefore computed more quickly, ++ * and block updates are sent only to non-redstone blocks, many of which may perform an ++ * action when informed of a change in redstone power level. (Note: Block updates are not ++ * the same as state changes to redstone wire. Wire block states are updated as soon ++ * as they are computed.) ++ * - Of the 36 block updates generated by a call to World.notifyNeighborsOfStateChange, ++ * 12 of them are obviously redundant (e.g. the west neighbor of the east neighbor). ++ * These are eliminated. ++ * - Updates to redstone wire and other connected blocks are propagated in a breath-first ++ * manner, radiating out from the initial trigger (a block update to a redstone wire ++ * from something other than redstone wire). ++ * - Updates are scheduled both deterministically and in an intuitive order, addressing bug ++ * MC-11193. ++ * - All redstone behavior that used to be locational now works the same in all locations. ++ * - All behaviors of redstone wire that used to be orientational now work the same in all ++ * orientations, as long as orientation can be determined; random otherwise. Some other ++ * redstone components still update directionally (e.g. switches), and this code can't ++ * compensate for that. ++ * - Information that is otherwise computed over and over again or which is expensive to ++ * to compute is cached for faster lookup. This includes coordinates of block position ++ * neighbors and block states that won't change behind our backs during the execution of ++ * this search algorithm. ++ * - Redundant block updates (both to redstone wire and to other blocks) are heavily ++ * consolidated. For worst-case scenarios (depowering of redstone wire) this results ++ * in a reduction of block updates by as much as 95% (factor of 1/21). Due to overheads, ++ * empirical testing shows a speedup better than 10x. This addresses bug MC-81098. ++ * ++ * Extensive testing has been performed to ensure that existing redstone contraptions still ++ * behave as expected. Results of early testing that identified undesirable behavior changes ++ * were addressed. Additionally, real-time performance testing revealed compute inefficiencies ++ * With earlier implementations of this accelerator. Some compatibility adjustments and ++ * performance optimizations resulted in harmless increases in block updates above the ++ * theoretical minimum. ++ * ++ * Only a single redstone machine was found to break: An instant dropper line hack that ++ * relies on powered rails and quasi-connectivity but doesn't work in all directions. The ++ * replacement is to lay redstone wire directly on top of the dropper line, which now works ++ * reliably in any direction. ++ * ++ * There are numerous other optimization that can be made, but those will be provided later in ++ * separate updates. This version is designed to be minimalistic. ++ * ++ * Many thanks to the following individuals for their help in testing this functionality: ++ * - pokechu22, _MethodZz_, WARBEN, NarcolepticFrog, CommandHelper (nessie), ilmango, ++ * OreoLamp, Xcom6000, tryashtar, RedCMD, Smokey95Dog, EDDxample, Rays Works, ++ * Nodnam, BlockyPlays, Grumm, NeunEinser, HelVince. ++ */ ++ ++ /* Reference to BlockRedstoneWire object, which uses this accelerator */ ++ private final BlockRedstoneWire wire; ++ ++ /* ++ * Implementation: ++ * ++ * RedstoneWire Blocks are updated in concentric rings or "layers" radiating out from the ++ * initial block update that came from a call to BlockRedstoneWire.neighborChanged(). ++ * All nodes put in Layer N are those with Manhattan distance N from the trigger ++ * position, reachable through connected redstone wire blocks. ++ * ++ * Layer 0 represents the trigger block position that was input to neighborChanged. ++ * Layer 1 contains the immediate neighbors of that position. ++ * Layer N contains the neighbors of blocks in layer N-1, not including ++ * those in previous layers. ++ * ++ * Layers enforce an update order that is a function of Manhattan distance ++ * from the initial coordinates input to neighborChanged. The same ++ * coordinates may appear in multiple layers, but redundant updates are minimized. ++ * Block updates are sent layer-by-layer. If multiple of a block's neighbors experience ++ * redstone wire changes before its layer is processed, then those updates will be merged. ++ * If a block's update has been sent, but its neighboring redstone changes ++ * after that, then another update will be sent. This preserves compatibility with ++ * machines that rely on zero-tick behavior, except that the new functionality is non- ++ * locational. ++ * ++ * Within each layer, updates are ordered left-to-right relative to the direction of ++ * information flow. This makes the implementation non-orientational. Only when ++ * this direction is ambiguous is randomness applied (intentionally). ++ */ ++ private List updateQueue0 = Lists.newArrayList(); ++ private List updateQueue1 = Lists.newArrayList(); ++ private List updateQueue2 = Lists.newArrayList(); ++ ++ public RedstoneWireTurbo(BlockRedstoneWire wire) { ++ this.wire = wire; ++ } ++ ++ /* ++ * Compute neighbors of a block. When a redstone wire value changes, previously it called ++ * World.notifyNeighborsOfStateChange. That lists immediately neighboring blocks in ++ * west, east, down, up, north, south order. For each of those neighbors, their own ++ * neighbors are updated in the same order. This generates 36 updates, but 12 of them are ++ * redundant; for instance the west neighbor of a block's east neighbor. ++ * ++ * Note that this ordering is only used to create the initial list of neighbors. Once ++ * the direction of signal flow is identified, the ordering of updates is completely ++ * reorganized. ++ */ ++ public static BlockPosition[] computeAllNeighbors(final BlockPosition pos) { ++ final int x = pos.getX(); ++ final int y = pos.getY(); ++ final int z = pos.getZ(); ++ final BlockPosition[] n = new BlockPosition[24]; ++ ++ // Immediate neighbors, in the same order as ++ // World.notifyNeighborsOfStateChange, etc.: ++ // west, east, down, up, north, south ++ n[0] = new BlockPosition(x - 1, y, z); ++ n[1] = new BlockPosition(x + 1, y, z); ++ n[2] = new BlockPosition(x, y - 1, z); ++ n[3] = new BlockPosition(x, y + 1, z); ++ n[4] = new BlockPosition(x, y, z - 1); ++ n[5] = new BlockPosition(x, y, z + 1); ++ ++ // Neighbors of neighbors, in the same order, ++ // except that duplicates are not included ++ n[6] = new BlockPosition(x - 2, y, z); ++ n[7] = new BlockPosition(x - 1, y - 1, z); ++ n[8] = new BlockPosition(x - 1, y + 1, z); ++ n[9] = new BlockPosition(x - 1, y, z - 1); ++ n[10] = new BlockPosition(x - 1, y, z + 1); ++ n[11] = new BlockPosition(x + 2, y, z); ++ n[12] = new BlockPosition(x + 1, y - 1, z); ++ n[13] = new BlockPosition(x + 1, y + 1, z); ++ n[14] = new BlockPosition(x + 1, y, z - 1); ++ n[15] = new BlockPosition(x + 1, y, z + 1); ++ n[16] = new BlockPosition(x, y - 2, z); ++ n[17] = new BlockPosition(x, y - 1, z - 1); ++ n[18] = new BlockPosition(x, y - 1, z + 1); ++ n[19] = new BlockPosition(x, y + 2, z); ++ n[20] = new BlockPosition(x, y + 1, z - 1); ++ n[21] = new BlockPosition(x, y + 1, z + 1); ++ n[22] = new BlockPosition(x, y, z - 2); ++ n[23] = new BlockPosition(x, y, z + 2); ++ return n; ++ } ++ ++ /* ++ * We only want redstone wires to update redstone wires that are ++ * immediately adjacent. Some more distant updates can result ++ * in cross-talk that (a) wastes time and (b) can make the update ++ * order unintuitive. Therefore (relative to the neighbor order ++ * computed by computeAllNeighbors), updates are not scheduled ++ * for redstone wire in those non-connecting positions. On the ++ * other hand, updates will always be sent to *other* types of blocks ++ * in any of the 24 neighboring positions. ++ */ ++ private static final boolean[] update_redstone = { ++ true, true, false, false, true, true, // 0 to 5 ++ false, true, true, false, false, false, // 6 to 11 ++ true, true, false, false, false, true, // 12 to 17 ++ true, false, true, true, false, false // 18 to 23 ++ }; ++ ++ // Internal numbering for cardinal directions ++ private static final int North = 0; ++ private static final int East = 1; ++ private static final int South = 2; ++ private static final int West = 3; ++ ++ /* ++ * These lookup tables completely remap neighbor positions into a left-to-right ++ * ordering, based on the cardinal direction that is determined to be forward. ++ * See below for more explanation. ++ */ ++ private static final int[] forward_is_north = {2, 3, 16, 19, 0, 4, 1, 5, 7, 8, 17, 20, 12, 13, 18, 21, 6, 9, 22, 14, 11, 10, 23, 15}; ++ private static final int[] forward_is_east = {2, 3, 16, 19, 4, 1, 5, 0, 17, 20, 12, 13, 18, 21, 7, 8, 22, 14, 11, 15, 23, 9, 6, 10}; ++ private static final int[] forward_is_south = {2, 3, 16, 19, 1, 5, 0, 4, 12, 13, 18, 21, 7, 8, 17, 20, 11, 15, 23, 10, 6, 14, 22, 9}; ++ private static final int[] forward_is_west = {2, 3, 16, 19, 5, 0, 4, 1, 18, 21, 7, 8, 17, 20, 12, 13, 23, 10, 6, 9, 22, 15, 11, 14}; ++ ++ /* For any orientation, we end up with the update order defined below. This order is relative to any redstone wire block ++ * that is itself having an update computed, and this center position is marked with C. ++ * - The update position marked 0 is computed first, and the one marked 23 is last. ++ * - Forward is determined by the local direction of information flow into position C from prior updates. ++ * - The first updates are scheduled for the four positions below and above C. ++ * - Then updates are scheduled for the four horizontal neighbors of C, followed by the positions below and above those neighbors. ++ * - Finally, updates are scheduled for the remaining positions with Manhattan distance 2 from C (at the same Y coordinate). ++ * - For a given horizontal distance from C, updates are scheduled starting from directly left and stepping clockwise to directly ++ * right. The remaining positions behind C are scheduled counterclockwise so as to maintain the left-to-right ordering. ++ * - If C is in layer N of the update schedule, then all 24 positions may be scheduled for layer N+1. For redstone wire, no ++ * updates are scheduled for positions that cannot directly connect. Additionally, the four positions above and below C ++ * are ALSO scheduled for layer N+2. ++ * - This update order was selected after experimenting with a number of alternative schedules, based on its compatibility ++ * with existing redstone designs and behaviors that were considered to be intuitive by various testers. WARBEN in particular ++ * made some of the most challenging test cases, but the 3-tick clocks (made by RedCMD) were also challenging to fix, ++ * along with the rail-based instant dropper line built by ilmango. Numerous others made test cases as well, including ++ * NarcolepticFrog, nessie, and Pokechu22. ++ * ++ * - The forward direction is determined locally. So when there are branches in the redstone wire, the left one will get updated ++ * before the right one. Each branch can have its own relative forward direction, resulting in the left side of a left branch ++ * having priority over the right branch of a left branch, which has priority over the left branch of a right branch, followed ++ * by the right branch of a right branch. And so forth. Since redstone power reduces to zero after a path distance of 15, ++ * that imposes a practical limit on the branching. Note that the branching is not tracked explicitly -- relative forward ++ * directions dictate relative sort order, which maintains the proper global ordering. This also makes it unnecessary to be ++ * concerned about branches meeting up with each other. ++ * ++ * ^ ++ * | ++ * Forward ++ * <-- Left Right --> ++ * ++ * 18 ++ * 10 17 5 19 11 ++ * 2 8 0 12 16 4 C 6 20 9 1 13 3 ++ * 14 21 7 23 15 ++ * Further 22 Further ++ * Down Down Up Up ++ * ++ * Backward ++ * | ++ * V ++ */ ++ ++ // This allows the above remapping tables to be looked up by cardial direction index ++ private static final int[][] reordering = { forward_is_north, forward_is_east, forward_is_south, forward_is_west }; ++ ++ /* ++ * Input: Array of UpdateNode objects in an order corresponding to the positions ++ * computed by computeAllNeighbors above. ++ * Output: Array of UpdateNode objects oriented using the above remapping tables ++ * corresponding to the identified heading (direction of information flow). ++ */ ++ private static void orientNeighbors(final UpdateNode[] src, final UpdateNode[] dst, final int heading) { ++ final int[] re = reordering[heading]; ++ for (int i = 0; i < 24; i++) { ++ dst[i] = src[re[i]]; ++ } ++ } ++ ++ /* ++ * Structure to keep track of redstone wire blocks and ++ * neighbors that will receive updates. ++ */ ++ private static class UpdateNode { ++ public static enum Type { ++ UNKNOWN, REDSTONE, OTHER ++ } ++ ++ IBlockData currentState; // Keep track of redstone wire value ++ UpdateNode[] neighbor_nodes; // References to neighbors (directed graph edges) ++ BlockPosition self; // UpdateNode's own position ++ BlockPosition parent; // Which block pos spawned/updated this node ++ Type type = Type.UNKNOWN; // unknown, redstone wire, other type of block ++ int layer; // Highest layer this node is scheduled in ++ boolean visited; // To keep track of information flow direction, visited restone wire is marked ++ int xbias, zbias; // Remembers directionality of ancestor nodes; helps eliminate directional ambiguities. ++ } ++ ++ /* ++ * Keep track of all block positions discovered during search and their current states. ++ * We want to remember one entry for each position. ++ */ ++ private final Map nodeCache = Maps.newHashMap(); ++ ++ /* ++ * For a newly created UpdateNode object, determine what type of block it is. ++ */ ++ private void identifyNode(final World worldIn, final UpdateNode upd1) { ++ final BlockPosition pos = upd1.self; ++ final IBlockData oldState = worldIn.getType(pos); ++ upd1.currentState = oldState; ++ ++ // Some neighbors of redstone wire are other kinds of blocks. ++ // These need to receive block updates to inform them that ++ // redstone wire values have changed. ++ final Block block = oldState.getBlock(); ++ if (block != wire) { ++ // Mark this block as not redstone wire and therefore ++ // requiring updates ++ upd1.type = UpdateNode.Type.OTHER; ++ ++ // Non-redstone blocks may propagate updates, but those updates ++ // are not handled by this accelerator. Therefore, we do not ++ // expand this position's neighbors. ++ return; ++ } ++ ++ // One job of BlockRedstoneWire.neighborChanged is to convert ++ // redstone wires to items if the block beneath was removed. ++ // With this accelerator, BlockRedstoneWire.neighborChanged ++ // is only typically called for a single wire block, while ++ // others are processed internally by the breadth first search ++ // algorithm. To preserve this game behavior, this check must ++ // be replicated here. ++ if (!wire.canPlace(null, worldIn, pos)) { ++ // Pop off the redstone dust ++ Block.a(worldIn, pos, new ItemStack(Items.REDSTONE)); // TODO ++ worldIn.setAir(pos); ++ ++ // Mark this position as not being redstone wire ++ upd1.type = UpdateNode.Type.OTHER; ++ ++ // Note: Sending updates to air blocks leads to an empty method. ++ // Testing shows this to be faster than explicitly avoiding updates to ++ // air blocks. ++ return; ++ } ++ ++ // If the above conditions fail, then this is a redstone wire block. ++ upd1.type = UpdateNode.Type.REDSTONE; ++ } ++ ++ /* ++ * Given which redstone wire blocks have been visited and not visited ++ * around the position currently being updated, compute the cardinal ++ * direction that is "forward." ++ * ++ * rx is the forward direction along the West/East axis ++ * rz is the forward direction along the North/South axis ++ */ ++ static private int computeHeading(final int rx, final int rz) { ++ // rx and rz can only take on values -1, 0, and 1, so we can ++ // compute a code number that allows us to use a single switch ++ // to determine the heading. ++ final int code = (rx + 1) + 3 * (rz + 1); ++ switch (code) { ++ case 0: { ++ // Both rx and rz are -1 (northwest) ++ // Randomly choose one to be forward. ++ final int j = ThreadLocalRandom.current().nextInt(0, 1); ++ return (j == 0) ? North : West; ++ } ++ case 1: { ++ // rx=0, rz=-1 ++ // Definitively North ++ return North; ++ } ++ case 2: { ++ // rx=1, rz=-1 (northeast) ++ // Choose randomly between north and east ++ final int j = ThreadLocalRandom.current().nextInt(0, 1); ++ return (j == 0) ? North : East; ++ } ++ case 3: { ++ // rx=-1, rz=0 ++ // Definitively West ++ return West; ++ } ++ case 4: { ++ // rx=0, rz=0 ++ // Heading is completely ambiguous. Choose ++ // randomly among the four cardinal directions. ++ return ThreadLocalRandom.current().nextInt(0, 4); ++ } ++ case 5: { ++ // rx=1, rz=0 ++ // Definitively East ++ return East; ++ } ++ case 6: { ++ // rx=-1, rz=1 (southwest) ++ // Choose randomly between south and west ++ final int j = ThreadLocalRandom.current().nextInt(0, 1); ++ return (j == 0) ? South : West; ++ } ++ case 7: { ++ // rx=0, rz=1 ++ // Definitively South ++ return South; ++ } ++ case 8: { ++ // rx=1, rz=1 (southeast) ++ // Choose randomly between south and east ++ final int j = ThreadLocalRandom.current().nextInt(0, 1); ++ return (j == 0) ? South : East; ++ } ++ } ++ ++ // We should never get here ++ return ThreadLocalRandom.current().nextInt(0, 4); ++ } ++ ++ // Select whether to use updateSurroundingRedstone from BlockRedstoneWire (old) ++ // or this helper class (new) ++ private static final boolean old_current_change = false; ++ ++ /* ++ * Process a node whose neighboring redstone wire has experienced value changes. ++ */ ++ private void updateNode(final World worldIn, final UpdateNode upd1, final int layer) { ++ final BlockPosition pos = upd1.self; ++ ++ // Mark this redstone wire as having been visited so that it can be used ++ // to calculate direction of information flow. ++ upd1.visited = true; ++ ++ // Look up the last known state. ++ // Due to the way other redstone components are updated, we do not ++ // have to worry about a state changing behind our backs. The rare ++ // exception is handled by scheduleReentrantNeighborChanged. ++ final IBlockData oldState = upd1.currentState; ++ ++ // Ask the wire block to compute its power level from its neighbors. ++ // This will also update the wire's power level and return a new ++ // state if it has changed. When a wire power level is changed, ++ // calculateCurrentChanges will immediately update the block state in the world ++ // and return the same value here to be cached in the corresponding ++ // UpdateNode object. ++ IBlockData newState; ++ if (old_current_change) { ++ newState = wire.calculateCurrentChanges(worldIn, pos, pos, oldState); ++ } else { ++ // Looking up block state is slow. This accelerator includes a version of ++ // calculateCurrentChanges that uses cahed wire values for a ++ // significant performance boost. ++ newState = this.calculateCurrentChanges(worldIn, upd1); ++ } ++ ++ // Only inform neighbors if the state has changed ++ if (newState != oldState) { ++ // Store the new state ++ upd1.currentState = newState; ++ ++ // Inform neighbors of the change ++ propagateChanges(worldIn, upd1, layer); ++ } ++ } ++ ++ /* ++ * This identifies the neighboring positions of a new UpdateNode object, ++ * determines their types, and links those to into the graph. Then based on ++ * what nodes in the redstone wire graph have been visited, the neighbors ++ * are reordered left-to-right relative to the direction of information flow. ++ */ ++ private void findNeighbors(final World worldIn, final UpdateNode upd1) { ++ final BlockPosition pos = upd1.self; ++ ++ // Get the list of neighbor coordinates ++ final BlockPosition[] neighbors = computeAllNeighbors(pos); ++ ++ // Temporary array of neighbors in cardinal ordering ++ final UpdateNode[] neighbor_nodes = new UpdateNode[24]; ++ ++ // Target array of neighbors sorted left-to-right ++ upd1.neighbor_nodes = new UpdateNode[24]; ++ ++ for (int i=0; i<24; i++) { ++ // Look up each neighbor in the node cache ++ final BlockPosition pos2 = neighbors[i]; ++ UpdateNode upd2 = nodeCache.get(pos2); ++ if (upd2 == null) { ++ // If this is a previously unreached position, create ++ // a new update node, add it to the cache, and identify what it is. ++ upd2 = new UpdateNode(); ++ upd2.self = pos2; ++ upd2.parent = pos; ++ nodeCache.put(pos2, upd2); ++ identifyNode(worldIn, upd2); ++ } ++ ++ // For non-redstone blocks, any of the 24 neighboring positions ++ // should receive a block update. However, some block coordinates ++ // may contain a redstone wire that does not directly connect to the ++ // one being expanded. To avoid redundant calculations and confusing ++ // cross-talk, those neighboring positions are not included. ++ if (update_redstone[i] || upd2.type != UpdateNode.Type.REDSTONE) { ++ neighbor_nodes[i] = upd2; ++ } ++ } ++ ++ // Determine the directions from which the redstone signal may have come from. This ++ // checks for redstone wire at the same Y level and also Y+1 and Y-1, relative to the ++ // block being expanded. ++ final boolean fromWest = (neighbor_nodes[0].visited || neighbor_nodes[7].visited || neighbor_nodes[8].visited); ++ final boolean fromEast = (neighbor_nodes[1].visited || neighbor_nodes[12].visited || neighbor_nodes[13].visited); ++ final boolean fromNorth = (neighbor_nodes[4].visited || neighbor_nodes[17].visited || neighbor_nodes[20].visited); ++ final boolean fromSouth = (neighbor_nodes[5].visited || neighbor_nodes[18].visited || neighbor_nodes[21].visited); ++ ++ int cx = 0, cz = 0; ++ if (fromWest) cx += 1; ++ if (fromEast) cx -= 1; ++ if (fromNorth) cz += 1; ++ if (fromSouth) cz -= 1; ++ ++ int heading; ++ if (cx==0 && cz==0) { ++ // If there is no clear direction, try to inherit the heading from ancestor nodes. ++ heading = computeHeading(upd1.xbias, upd1.zbias); ++ ++ // Propagate that heading to descendant nodes. ++ for (int i=0; i<24; i++) { ++ final UpdateNode nn = neighbor_nodes[i]; ++ if (nn != null) { ++ nn.xbias = upd1.xbias; ++ nn.zbias = upd1.zbias; ++ } ++ } ++ } else { ++ if (cx != 0 && cz != 0) { ++ // If the heading is somewhat ambiguous, try to disambiguate based on ++ // ancestor nodes. ++ if (upd1.xbias != 0) cz = 0; ++ if (upd1.zbias != 0) cx = 0; ++ } ++ heading = computeHeading(cx, cz); ++ ++ // Propagate that heading to descendant nodes. ++ for (int i=0; i<24; i++) { ++ final UpdateNode nn = neighbor_nodes[i]; ++ if (nn != null) { ++ nn.xbias = cx; ++ nn.zbias = cz; ++ } ++ } ++ } ++ ++ // Reorder neighboring UpdateNode objects according to the forward direction ++ // determined above. ++ orientNeighbors(neighbor_nodes, upd1.neighbor_nodes, heading); ++ } ++ ++ /* ++ * For any redstone wire block in layer N, inform neighbors to recompute their states ++ * in layers N+1 and N+2; ++ */ ++ private void propagateChanges(final World worldIn, final UpdateNode upd1, final int layer) { ++ if (upd1.neighbor_nodes == null) { ++ // If this node has not been expanded yet, find its neighbors ++ findNeighbors(worldIn, upd1); ++ } ++ ++ final BlockPosition pos = upd1.self; ++ ++ // All neighbors may be scheduled for layer N+1 ++ final int layer1 = layer + 1; ++ ++ // If the node being updated (upd1) has already been expanded, then merely ++ // schedule updates to its neighbors. ++ for (int i = 0; i < 24; i++) { ++ final UpdateNode upd2 = upd1.neighbor_nodes[i]; ++ ++ // This test ensures that an UpdateNode is never scheduled to the same layer ++ // more than once. Also, skip non-connecting redstone wire blocks ++ if (upd2 != null && layer1 > upd2.layer) { ++ upd2.layer = layer1; ++ updateQueue1.add(upd2); ++ ++ // Keep track of which block updated this neighbor ++ upd2.parent = pos; ++ } ++ } ++ ++ // Nodes above and below are scheduled ALSO for layer N+2 ++ final int layer2 = layer + 2; ++ ++ // Repeat of the loop above, but only for the first four (above and below) neighbors ++ // and for layer N+2; ++ for (int i = 0; i < 4; i++) { ++ final UpdateNode upd2 = upd1.neighbor_nodes[i]; ++ if (upd2 != null && layer2 > upd2.layer) { ++ upd2.layer = layer2; ++ updateQueue2.add(upd2); ++ upd2.parent = pos; ++ } ++ } ++ } ++ ++ // The breadth-first search below will send block updates to blocks ++ // that are not redstone wire. If one of those updates results in ++ // a distant redstone wire getting an update, then this.neighborChanged ++ // will get called. This would be a reentrant call, and ++ // it is necessary to properly integrate those updates into the ++ // on-going search through redstone wire. Thus, we make the layer ++ // currently being processed visible at the object level. ++ ++ // The current layer being processed by the breadth-first search ++ private int currentWalkLayer = 0; ++ ++ private void shiftQueue() { ++ final List t = updateQueue0; ++ t.clear(); ++ updateQueue0 = updateQueue1; ++ updateQueue1 = updateQueue2; ++ updateQueue2 = t; ++ } ++ ++ /* ++ * Perform a breadth-first (layer by layer) traversal through redstone ++ * wire blocks, propagating value changes to neighbors in an order ++ * that is a function of distance from the initial call to ++ * this.neighborChanged. ++ */ ++ private void breadthFirstWalk(final World worldIn) { ++ shiftQueue(); ++ currentWalkLayer = 1; ++ ++ // Loop over all layers ++ while (updateQueue0.size()>0 || updateQueue1.size()>0) { ++ // Get the set of blocks in this layer ++ final List thisLayer = updateQueue0; ++ ++ // Loop over all blocks in the layer. Recall that ++ // this is a List, preserving the insertion order of ++ // left-to-right based on direction of information flow. ++ for (UpdateNode upd : thisLayer) { ++ if (upd.type == UpdateNode.Type.REDSTONE) { ++ // If the node is is redstone wire, ++ // schedule updates to neighbors if its value ++ // has changed. ++ updateNode(worldIn, upd, currentWalkLayer); ++ } else { ++ // If this block is not redstone wire, send a block update. ++ // Redstone wire blocks get state updates, but they don't ++ // need block updates. Only non-redstone neighbors need updates. ++ ++ // World.neighborChanged is called from ++ // World.notifyNeighborsOfStateChange, and ++ // notifyNeighborsOfStateExcept. We don't use ++ // World.notifyNeighborsOfStateChange here, since we are ++ // already keeping track of all of the neighbor positions ++ // that need to be updated. All on its own, handling neighbors ++ // this way reduces block updates by 1/3 (24 instead of 36). ++ worldIn.neighborChanged(upd.self, wire, upd.parent); ++ } ++ } ++ ++ // Move on to the next layer ++ shiftQueue(); ++ currentWalkLayer++; ++ } ++ ++ currentWalkLayer = 0; ++ } ++ ++ /* ++ * Normally, when Minecraft is computing redstone wire power changes, and a wire power level ++ * change sends a block update to a neighboring functional component (e.g. piston, repeater, etc.), ++ * those updates are queued. Only once all redstone wire updates are complete will any component ++ * action generate any further block updates to redstone wire. Instant repeater lines, for instance, ++ * will process all wire updates for one redstone line, after which the pistons will zero-tick, ++ * after which the next redstone line performs all of its updates. Thus, each wire is processed in its ++ * own discrete wave. ++ * ++ * However, there are some corner cases where this pattern breaks, with a proof of concept discovered ++ * by Rays Works, which works the same in vanilla. The scenario is as follows: ++ * (1) A redstone wire is conducting a signal. ++ * (2) Part-way through that wave of updates, a neighbor is updated that causes an update to a completely ++ * separate redstone wire. ++ * (3) This results in a call to BlockRedstoneWire.neighborChanged for that other wire, in the middle of ++ * an already on-going propagation through the first wire. ++ * ++ * The vanilla code, being depth-first, would end up fully processing the second wire before going back ++ * to finish processing the first one. (Although technically, vanilla has no special concept of "being ++ * in the middle" of processing updates to a wire.) For the breadth-first algorithm, we give this ++ * situation special handling, where the updates for the second wire are incorporated into the schedule ++ * for the first wire, and then the callstack is allowed to unwind back to the on-going search loop in ++ * order to continue processing both the first and second wire in the order of distance from the initial ++ * trigger. ++ */ ++ private IBlockData scheduleReentrantNeighborChanged(final World worldIn, final BlockPosition pos, final IBlockData newState, final BlockPosition source) { ++ if (source != null) { ++ // If the cause of the redstone wire update is known, we can use that to help determine ++ // direction of information flow. ++ UpdateNode src = nodeCache.get(source); ++ if (src == null) { ++ src = new UpdateNode(); ++ src.self = source; ++ src.parent = source; ++ src.visited = true; ++ identifyNode(worldIn, src); ++ nodeCache.put(source, src); ++ } ++ } ++ ++ // Find or generate a node for the redstone block position receiving the update ++ UpdateNode upd = nodeCache.get(pos); ++ if (upd == null) { ++ upd = new UpdateNode(); ++ upd.self = pos; ++ upd.parent = pos; ++ upd.visited = true; ++ identifyNode(worldIn, upd); ++ nodeCache.put(pos, upd); ++ } ++ upd.currentState = newState; ++ ++ // Receiving this block update may mean something in the world changed. ++ // Therefore we clear the cached block info about all neighbors of ++ // the position receiving the update and then re-identify what they are. ++ if (upd.neighbor_nodes != null) { ++ for (int i=0; i<24; i++) { ++ final UpdateNode upd2 = upd.neighbor_nodes[i]; ++ if (upd2 == null) continue; ++ upd2.type = UpdateNode.Type.UNKNOWN; ++ upd2.currentState = null; ++ identifyNode(worldIn, upd2); ++ } ++ } ++ ++ // The block at 'pos' is a redstone wire and has been updated already by calling ++ // wire.calculateCurrentChanges, so we don't schedule that. However, we do need ++ // to schedule its neighbors. By passing the current value of 'currentWalkLayer' to ++ // propagateChanges, the neighbors of 'pos' are scheduled for layers currentWalkLayer+1 ++ // and currentWalkLayer+2. ++ propagateChanges(worldIn, upd, currentWalkLayer); ++ ++ // Return here. The call stack will unwind back to the first call to ++ // updateSurroundingRedstone, whereupon the new updates just scheduled will ++ // be propagated. This also facilitates elimination of superfluous and ++ // redundant block updates. ++ return newState; ++ } ++ ++ /* ++ * New version of pre-existing updateSurroundingRedstone, which is called from ++ * wire.updateSurroundingRedstone, which is called from wire.neighborChanged and a ++ * few other methods in BlockRedstoneWire. This sets off the breadth-first ++ * walk through all redstone dust connected to the initial position triggered. ++ */ ++ public IBlockData updateSurroundingRedstone(final World worldIn, final BlockPosition pos, final IBlockData state, final BlockPosition source) { ++ // Check this block's neighbors and see if its power level needs to change ++ // Use the calculateCurrentChanges method in BlockRedstoneWire since we have no ++ // cached block states at this point. ++ final IBlockData newState = wire.calculateCurrentChanges(worldIn, pos, pos, state); ++ ++ // If no change, exit ++ if (newState == state) { ++ return state; ++ } ++ ++ // Check to see if this update was received during an on-going breadth first search ++ if (currentWalkLayer > 0 || nodeCache.size() > 0) { ++ // As breadthFirstWalk progresses, it sends block updates to neighbors. Some of those ++ // neighbors may affect the world so as to cause yet another redstone wire block to receive ++ // an update. If that happens, we need to integrate those redstone wire updates into the ++ // already on-going graph walk being performed by breadthFirstWalk. ++ return scheduleReentrantNeighborChanged(worldIn, pos, newState, source); ++ } ++ // If there are no on-going walks through redstone wire, then start a new walk. ++ ++ // If the source of the block update to the redstone wire at 'pos' is known, we can use ++ // that to help determine the direction of information flow. ++ if (source != null) { ++ final UpdateNode src = new UpdateNode(); ++ src.self = source; ++ src.parent = source; ++ src.visited = true; ++ nodeCache.put(source, src); ++ identifyNode(worldIn, src); ++ } ++ ++ // Create a node representing the block at 'pos', and then propagate updates ++ // to its neighbors. As stated above, the call to wire.calculateCurrentChanges ++ // already performs the update to the block at 'pos', so it is not added to the schedule. ++ final UpdateNode upd = new UpdateNode(); ++ upd.self = pos; ++ upd.parent = source!=null ? source : pos; ++ upd.currentState = newState; ++ upd.type = UpdateNode.Type.REDSTONE; ++ upd.visited = true; ++ nodeCache.put(pos, upd); ++ propagateChanges(worldIn, upd, 0); ++ ++ // Perform the walk over all directly reachable redstone wire blocks, propagating wire value ++ // updates in a breadth first order out from the initial update received for the block at 'pos'. ++ breadthFirstWalk(worldIn); ++ ++ // With the whole search completed, clear the list of all known blocks. ++ // We do not want to keep around state information that may be changed by other code. ++ // In theory, we could cache the neighbor block positions, but that is a separate ++ // optimization. ++ nodeCache.clear(); ++ ++ return newState; ++ } ++ ++ // For any array of neighbors in an UpdateNode object, these are always ++ // the indices of the four immediate neighbors at the same Y coordinate. ++ private static final int[] rs_neighbors = {4, 5, 6, 7}; ++ private static final int[] rs_neighbors_up = {9, 11, 13, 15}; ++ private static final int[] rs_neighbors_dn = {8, 10, 12, 14}; ++ ++ /* ++ * Updated calculateCurrentChanges that is optimized for speed and uses ++ * the UpdateNode's neighbor array to find the redstone states of neighbors ++ * that might power it. ++ */ ++ private IBlockData calculateCurrentChanges(final World worldIn, final UpdateNode upd) { ++ IBlockData state = upd.currentState; ++ final int i = state.get(BlockRedstoneWire.POWER).intValue(); ++ int j = 0; ++ j = getMaxCurrentStrength(upd, j); ++ int l = 0; ++ ++ wire.setCanProvidePower(false); ++ // Unfortunately, World.isBlockIndirectlyGettingPowered is complicated, ++ // and I'm not ready to try to replicate even more functionality from ++ // elsewhere in Minecraft into this accelerator. So sadly, we must ++ // suffer the performance hit of this very expensive call. If there ++ // is consistency to what this call returns, we may be able to cache it. ++ final int k = worldIn.isBlockIndirectlyGettingPowered(upd.self); ++ wire.setCanProvidePower(true); ++ ++ // The variable 'k' holds the maximum redstone power value of any adjacent blocks. ++ // If 'k' has the highest level of all neighbors, then the power level of this ++ // redstone wire will be set to 'k'. If 'k' is already 15, then nothing inside the ++ // following loop can affect the power level of the wire. Therefore, the loop is ++ // skipped if k is already 15. ++ if (k < 15) { ++ if (upd.neighbor_nodes == null) { ++ // If this node's neighbors are not known, expand the node ++ findNeighbors(worldIn, upd); ++ } ++ ++ // These remain constant, so pull them out of the loop. ++ // Regardless of which direction is forward, the UpdateNode for the ++ // position directly above the node being calculated is always ++ // at index 1. ++ UpdateNode center_up = upd.neighbor_nodes[1]; ++ boolean center_up_is_cube = center_up.currentState.isOccluding(worldIn, center_up.self); // TODO ++ ++ for (int m = 0; m < 4; m++) { ++ // Get the neighbor array index of each of the four cardinal ++ // neighbors. ++ int n = rs_neighbors[m]; ++ ++ // Get the max redstone power level of each of the cardinal ++ // neighbors ++ UpdateNode neighbor = upd.neighbor_nodes[n]; ++ l = getMaxCurrentStrength(neighbor, l); ++ ++ // Also check the positions above and below the cardinal ++ // neighbors ++ boolean neighbor_is_cube = neighbor.currentState.isOccluding(worldIn, neighbor.self); // TODO ++ if (!neighbor_is_cube) { ++ UpdateNode neighbor_down = upd.neighbor_nodes[rs_neighbors_dn[m]]; ++ l = getMaxCurrentStrength(neighbor_down, l); ++ } else ++ if (!center_up_is_cube) { ++ UpdateNode neighbor_up = upd.neighbor_nodes[rs_neighbors_up[m]]; ++ l = getMaxCurrentStrength(neighbor_up, l); ++ } ++ } ++ } ++ ++ // The new code sets this RedstoneWire block's power level to the highest neighbor ++ // minus 1. This usually results in wire power levels dropping by 2 at a time. ++ // This optimization alone has no impact on update order, only the number of updates. ++ j = l - 1; ++ ++ // If 'l' turns out to be zero, then j will be set to -1, but then since 'k' will ++ // always be in the range of 0 to 15, the following if will correct that. ++ if (k > j) j = k; ++ ++ // egg82's amendment ++ // Adding Bukkit's BlockRedstoneEvent - er.. event. ++ if (i != j) { ++ BlockRedstoneEvent event = new BlockRedstoneEvent(worldIn.getWorld().getBlockAt(upd.self.getX(), upd.self.getY(), upd.self.getZ()), i, j); ++ worldIn.getServer().getPluginManager().callEvent(event); ++ j = event.getNewCurrent(); ++ } ++ ++ if (i != j) { ++ // If the power level has changed from its previous value, compute a new state ++ // and set it in the world. ++ // Possible optimization: Don't commit state changes to the world until they ++ // need to be known by some nearby non-redstone-wire block. ++ BlockPosition pos = new BlockPosition(upd.self.getX(), upd.self.getY(), upd.self.getZ()); ++ if (wire.canPlace(null, worldIn, pos)) { ++ state = state.set(BlockRedstoneWire.POWER, Integer.valueOf(j)); ++ worldIn.setTypeAndData(upd.self, state, 2); ++ } ++ } ++ ++ return state; ++ } ++ ++ /* ++ * Optimized function to compute a redstone wire's power level based on cached ++ * state. ++ */ ++ private static int getMaxCurrentStrength(final UpdateNode upd, final int strength) { ++ if (upd.type != UpdateNode.Type.REDSTONE) return strength; ++ final int i = upd.currentState.get(BlockRedstoneWire.POWER).intValue(); ++ return i > strength ? i : strength; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 9369a0c6c0ae2d8518ebfb17f2c93ead2647ab8d..307a89c431739ed15d8869faace7b08927626f60 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -659,6 +659,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + } + ++ public void neighborChanged(BlockPosition pos, Block blockIn, BlockPosition fromPos) { a(pos, blockIn, fromPos); } // Paper - OBFHELPER + public void a(BlockPosition blockposition, Block block, BlockPosition blockposition1) { + if (!this.isClientSide) { + IBlockData iblockdata = this.getType(blockposition); +@@ -1287,6 +1288,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return this.getBlockFacePower(blockposition.down(), EnumDirection.DOWN) > 0 ? true : (this.getBlockFacePower(blockposition.up(), EnumDirection.UP) > 0 ? true : (this.getBlockFacePower(blockposition.north(), EnumDirection.NORTH) > 0 ? true : (this.getBlockFacePower(blockposition.south(), EnumDirection.SOUTH) > 0 ? true : (this.getBlockFacePower(blockposition.west(), EnumDirection.WEST) > 0 ? true : this.getBlockFacePower(blockposition.east(), EnumDirection.EAST) > 0)))); + } + ++ public int isBlockIndirectlyGettingPowered(BlockPosition pos) { return this.s(pos); } // Paper - OBFHELPER + public int s(BlockPosition blockposition) { + int i = 0; + EnumDirection[] aenumdirection = World.a; +diff --git a/src/main/java/net/minecraft/world/level/block/BlockRedstoneWire.java b/src/main/java/net/minecraft/world/level/block/BlockRedstoneWire.java +index 60f0e582199472f9c5ef26e12ee1da0c3867254e..71ba55d36f5bc8a417930015c31553b30cf3e8dd 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockRedstoneWire.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockRedstoneWire.java +@@ -1,5 +1,7 @@ + package net.minecraft.world.level.block; + ++import com.destroystokyo.paper.PaperConfig; ++import com.destroystokyo.paper.util.RedstoneWireTurbo; + import com.google.common.collect.ImmutableMap; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -50,7 +52,7 @@ public class BlockRedstoneWire extends Block { + private final Map j = Maps.newHashMap(); + private static final Vector3fa[] k = new Vector3fa[16]; + private final IBlockData o; +- private boolean p = true; ++ private boolean p = true; public final boolean canProvidePower() { return this.p; } public final void setCanProvidePower(boolean value) { this.p = value; } // Paper - OBFHELPER + + public BlockRedstoneWire(BlockBase.Info blockbase_info) { + super(blockbase_info); +@@ -237,6 +239,121 @@ public class BlockRedstoneWire extends Block { + return iblockdata.d(iblockaccess, blockposition, EnumDirection.UP) || iblockdata.a(Blocks.HOPPER); + } + ++ // Paper start - Optimize redstone ++ // The bulk of the new functionality is found in RedstoneWireTurbo.java ++ RedstoneWireTurbo turbo = new RedstoneWireTurbo(this); ++ ++ /* ++ * Modified version of pre-existing updateSurroundingRedstone, which is called from ++ * this.neighborChanged and a few other methods in this class. ++ * Note: Added 'source' argument so as to help determine direction of information flow ++ */ ++ private void updateSurroundingRedstone(World worldIn, BlockPosition pos, IBlockData state, BlockPosition source) { ++ if (worldIn.paperConfig.useEigencraftRedstone) { ++ turbo.updateSurroundingRedstone(worldIn, pos, state, source); ++ return; ++ } ++ a(worldIn, pos, state); ++ } ++ ++ /* ++ * Slightly modified method to compute redstone wire power levels from neighboring blocks. ++ * Modifications cut the number of power level changes by about 45% from vanilla, and this ++ * optimization synergizes well with the breadth-first search implemented in ++ * RedstoneWireTurbo. ++ * Note: RedstoneWireTurbo contains a faster version of this code. ++ * Note: Made this public so that RedstoneWireTurbo can access it. ++ */ ++ public IBlockData calculateCurrentChanges(World worldIn, BlockPosition pos1, BlockPosition pos2, IBlockData state) { ++ IBlockData iblockstate = state; ++ int i = state.get(POWER); ++ int j = 0; ++ j = this.getPower(j, worldIn.getType(pos2)); ++ this.setCanProvidePower(false); ++ int k = worldIn.isBlockIndirectlyGettingPowered(pos1); ++ this.setCanProvidePower(true); ++ ++ if (!worldIn.paperConfig.useEigencraftRedstone) { ++ // This code is totally redundant to if statements just below the loop. ++ if (k > 0 && k > j - 1) { ++ j = k; ++ } ++ } ++ ++ int l = 0; ++ ++ // The variable 'k' holds the maximum redstone power value of any adjacent blocks. ++ // If 'k' has the highest level of all neighbors, then the power level of this ++ // redstone wire will be set to 'k'. If 'k' is already 15, then nothing inside the ++ // following loop can affect the power level of the wire. Therefore, the loop is ++ // skipped if k is already 15. ++ if (!worldIn.paperConfig.useEigencraftRedstone || k < 15) { ++ for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) { ++ BlockPosition blockpos = pos1.shift(enumfacing); ++ boolean flag = blockpos.getX() != pos2.getX() || blockpos.getZ() != pos2.getZ(); ++ ++ if (flag) { ++ l = this.getPower(l, worldIn.getType(blockpos)); ++ } ++ ++ if (worldIn.getType(blockpos).isOccluding(worldIn, blockpos) && !worldIn.getType(pos1.up()).isOccluding(worldIn, pos1)) { ++ if (flag && pos1.getY() >= pos2.getY()) { ++ l = this.getPower(l, worldIn.getType(blockpos.up())); ++ } ++ } else if (!worldIn.getType(blockpos).isOccluding(worldIn, blockpos) && flag && pos1.getY() <= pos2.getY()) { ++ l = this.getPower(l, worldIn.getType(blockpos.down())); ++ } ++ } ++ } ++ ++ if (!worldIn.paperConfig.useEigencraftRedstone) { ++ // The old code would decrement the wire value only by 1 at a time. ++ if (l > j) { ++ j = l - 1; ++ } else if (j > 0) { ++ --j; ++ } else { ++ j = 0; ++ } ++ ++ if (k > j - 1) { ++ j = k; ++ } ++ } else { ++ // The new code sets this RedstoneWire block's power level to the highest neighbor ++ // minus 1. This usually results in wire power levels dropping by 2 at a time. ++ // This optimization alone has no impact on update order, only the number of updates. ++ j = l - 1; ++ ++ // If 'l' turns out to be zero, then j will be set to -1, but then since 'k' will ++ // always be in the range of 0 to 15, the following if will correct that. ++ if (k > j) j = k; ++ } ++ ++ if (i != j) { ++ state = state.set(POWER, j); ++ ++ if (worldIn.getType(pos1) == iblockstate) { ++ worldIn.setTypeAndData(pos1, state, 2); ++ } ++ ++ // 1.16(.1?) dropped the need for blocks needing updates. ++ // Whether this is necessary after all is to be seen. ++// if (!worldIn.paperConfig.useEigencraftRedstone) { ++// // The new search algorithm keeps track of blocks needing updates in its own data structures, ++// // so only add anything to blocksNeedingUpdate if we're using the vanilla update algorithm. ++// this.getBlocksNeedingUpdate().add(pos1); ++// ++// for (EnumDirection enumfacing1 : EnumDirection.values()) { ++// this.getBlocksNeedingUpdate().add(pos1.shift(enumfacing1)); ++// } ++// } ++ } ++ ++ return state; ++ } ++ // Paper end ++ + private void a(World world, BlockPosition blockposition, IBlockData iblockdata) { + int i = this.a(world, blockposition); + +@@ -306,6 +423,8 @@ public class BlockRedstoneWire extends Block { + return Math.max(i, j - 1); + } + ++ private int getPower(int min, IBlockData iblockdata) { return Math.max(min, getPower(iblockdata)); } // Paper - Optimize redstone ++ private int getPower(IBlockData iblockdata) { return this.o(iblockdata); } // Paper - OBFHELPER + private int o(IBlockData iblockdata) { + return iblockdata.a((Block) this) ? (Integer) iblockdata.get(BlockRedstoneWire.POWER) : 0; + } +@@ -328,7 +447,7 @@ public class BlockRedstoneWire extends Block { + @Override + public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { + if (!iblockdata1.a(iblockdata.getBlock()) && !world.isClientSide) { +- this.a(world, blockposition, iblockdata); ++ this.updateSurroundingRedstone(world, blockposition, iblockdata, null); // Paper - Optimize redstone + Iterator iterator = EnumDirection.EnumDirectionLimit.VERTICAL.iterator(); + + while (iterator.hasNext()) { +@@ -355,7 +474,7 @@ public class BlockRedstoneWire extends Block { + world.applyPhysics(blockposition.shift(enumdirection), this); + } + +- this.a(world, blockposition, iblockdata); ++ this.updateSurroundingRedstone(world, blockposition, iblockdata, null); // Paper - Optimize redstone + this.d(world, blockposition); + } + } +@@ -390,7 +509,7 @@ public class BlockRedstoneWire extends Block { + public void doPhysics(IBlockData iblockdata, World world, BlockPosition blockposition, Block block, BlockPosition blockposition1, boolean flag) { + if (!world.isClientSide) { + if (iblockdata.canPlace(world, blockposition)) { +- this.a(world, blockposition, iblockdata); ++ this.updateSurroundingRedstone(world, blockposition, iblockdata, blockposition1); // Paper - Optimize redstone + } else { + c(iblockdata, world, blockposition); + world.a(blockposition, false); diff --git a/patches/server-unmapped/0001/0562-Fix-hex-colors-not-working-in-some-kick-messages.patch b/patches/server-unmapped/0001/0562-Fix-hex-colors-not-working-in-some-kick-messages.patch new file mode 100644 index 0000000000..d8ff93e0d4 --- /dev/null +++ b/patches/server-unmapped/0001/0562-Fix-hex-colors-not-working-in-some-kick-messages.patch @@ -0,0 +1,64 @@ +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/HandshakeListener.java b/src/main/java/net/minecraft/server/network/HandshakeListener.java +index 6d001843d8f899e91f19c384ddf57e6987bfb79e..b97d289afdff81d9959e238639f4e3e186f8e9c8 100644 +--- a/src/main/java/net/minecraft/server/network/HandshakeListener.java ++++ b/src/main/java/net/minecraft/server/network/HandshakeListener.java +@@ -46,7 +46,7 @@ public class HandshakeListener implements PacketHandshakingInListener { + synchronized (throttleTracker) { + if (throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - throttleTracker.get(address) < connectionThrottle) { + throttleTracker.put(address, currentTime); +- ChatMessage chatmessage = new ChatMessage(com.destroystokyo.paper.PaperConfig.connectionThrottleKickMessage); // Paper - Configurable connection throttle kick message ++ IChatBaseComponent chatmessage = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(com.destroystokyo.paper.PaperConfig.connectionThrottleKickMessage, true)[0]; // Paper - Configurable connection throttle kick message // Paper - Fix hex colors not working in some kick messages + this.c.sendPacket(new PacketLoginOutDisconnect(chatmessage)); + this.c.close(chatmessage); + return; +@@ -72,12 +72,12 @@ public class HandshakeListener implements PacketHandshakingInListener { + } + // CraftBukkit end + if (packethandshakinginsetprotocol.c() != SharedConstants.getGameVersion().getProtocolVersion()) { +- ChatMessage chatmessage; ++ IChatBaseComponent chatmessage; // Paper - Fix hex colors not working in some kick messages + + if (packethandshakinginsetprotocol.c() < 754) { +- chatmessage = new ChatMessage( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getGameVersion().getName() ) ); // Spigot ++ chatmessage = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getGameVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages + } else { +- chatmessage = new ChatMessage( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getGameVersion().getName() ) ); // Spigot ++ chatmessage = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getGameVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages + } + + this.c.sendPacket(new PacketLoginOutDisconnect(chatmessage)); +@@ -94,7 +94,7 @@ public class HandshakeListener implements PacketHandshakingInListener { + if (event.callEvent()) { + // If we've failed somehow, let the client know so and go no further. + if (event.isFailed()) { +- chatmessage = new ChatMessage(event.getFailMessage()); ++ IChatBaseComponent chatmessage = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(event.getFailMessage(), true)[0]; // Paper - Fix hex colors not working in some kick messages + this.getNetworkManager().sendPacket(new PacketLoginOutDisconnect(chatmessage)); + this.getNetworkManager().close(chatmessage); + return; +diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java +index 39aa50a386d0ab94914cd8f85c69cf404e0dc4b2..185667110cd6f566b23546728d20fc79223f3c92 100644 +--- a/src/main/java/net/minecraft/server/network/LoginListener.java ++++ b/src/main/java/net/minecraft/server/network/LoginListener.java +@@ -106,14 +106,7 @@ public class LoginListener implements PacketLoginInListener { + // CraftBukkit start + @Deprecated + public void disconnect(String s) { +- try { +- IChatBaseComponent ichatbasecomponent = new ChatComponentText(s); +- LoginListener.LOGGER.info("Disconnecting {}: {}", this.d(), s); +- this.networkManager.sendPacket(new PacketLoginOutDisconnect(ichatbasecomponent)); +- this.networkManager.close(ichatbasecomponent); +- } catch (Exception exception) { +- LoginListener.LOGGER.error("Error whilst disconnecting player", exception); +- } ++ disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(s, true)[0]); // Paper - Fix hex colors not working in some kick messages + } + // CraftBukkit end + diff --git a/patches/server-unmapped/0001/0563-PortalCreateEvent-needs-to-know-its-entity.patch b/patches/server-unmapped/0001/0563-PortalCreateEvent-needs-to-know-its-entity.patch new file mode 100644 index 0000000000..51e748ada1 --- /dev/null +++ b/patches/server-unmapped/0001/0563-PortalCreateEvent-needs-to-know-its-entity.patch @@ -0,0 +1,134 @@ +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 158c7a30af8326fa419af391167c07d75b8611ac..fee862951f2767d4a3c5268dff157c185378a939 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -368,7 +368,7 @@ public final class ItemStack { + IBlockData block = world.getType(newblockposition); + + if (!(block.getBlock() instanceof BlockTileEntity)) { // Containers get placed automatically +- block.getBlock().onPlace(block, world, newblockposition, oldBlock, true); ++ block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, itemactioncontext); // Paper - pass itemactioncontext + } + + world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getType(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/BlockFire.java b/src/main/java/net/minecraft/world/level/block/BlockFire.java +index e6ea1d29c7f3f4cb6039df0e35c8db94b6f38c3e..70c32b7a53a1107cced3491ebac19b0eaf4fec2e 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockFire.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockFire.java +@@ -15,6 +15,7 @@ import net.minecraft.core.EnumDirection; + import net.minecraft.server.MCUtil; + import net.minecraft.server.level.WorldServer; + import net.minecraft.world.item.context.BlockActionContext; ++import net.minecraft.world.item.context.ItemActionContext; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.GeneratorAccess; + import net.minecraft.world.level.IBlockAccess; +@@ -364,8 +365,10 @@ public class BlockFire extends BlockFireAbstract { + } + + @Override +- public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { +- super.onPlace(iblockdata, world, blockposition, iblockdata1, flag); ++ // Paper start - ItemActionContext param ++ public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag, ItemActionContext itemActionContext) { ++ super.onPlace(iblockdata, world, blockposition, iblockdata1, flag, itemActionContext); ++ // Paper end + world.getBlockTickList().a(blockposition, this, a(world.random)); + } + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockFireAbstract.java b/src/main/java/net/minecraft/world/level/block/BlockFireAbstract.java +index b86513497b7ca8bde84176f5228ef9c479a73abb..02047bf07c2008c7de8daf3d76b660e25b77bce8 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockFireAbstract.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockFireAbstract.java +@@ -7,6 +7,7 @@ import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.item.context.BlockActionContext; ++import net.minecraft.world.item.context.ItemActionContext; + import net.minecraft.world.level.GeneratorAccess; + import net.minecraft.world.level.IBlockAccess; + import net.minecraft.world.level.World; +@@ -66,14 +67,17 @@ public abstract class BlockFireAbstract extends Block { + super.a(iblockdata, world, blockposition, entity); + } + ++ // Paper start - ItemActionContext param ++ @Override public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { this.onPlace(iblockdata, world, blockposition, iblockdata1, flag, null); } + @Override +- public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { ++ public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag, ItemActionContext itemActionContext) { ++ // Paper end + if (!iblockdata1.a(iblockdata.getBlock())) { + if (a(world)) { + Optional optional = BlockPortalShape.a((GeneratorAccess) world, blockposition, EnumDirection.EnumAxis.X); + + if (optional.isPresent()) { +- ((BlockPortalShape) optional.get()).createPortal(); ++ ((BlockPortalShape) optional.get()).createPortal(itemActionContext); // Paper - pass ItemActionContext param + return; + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +index 2a785ea58a7bdc80c703a60bc6ed602dc8040aa0..9af91784544dbb0555824a91088257659bdf2c3d 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +@@ -32,6 +32,7 @@ import net.minecraft.world.item.EnumColor; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.context.BlockActionContext; ++import net.minecraft.world.item.context.ItemActionContext; + import net.minecraft.world.level.BlockAccessAir; + import net.minecraft.world.level.GeneratorAccess; + import net.minecraft.world.level.IBlockAccess; +@@ -120,6 +121,12 @@ public abstract class BlockBase { + PacketDebug.a(world, blockposition); + } + ++ // Paper start - add ItemActionContext param ++ @Deprecated ++ public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag, ItemActionContext itemActionContext) { ++ this.onPlace(iblockdata, world, blockposition, iblockdata1, flag); ++ } ++ // Paper end + @Deprecated + public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { + org.spigotmc.AsyncCatcher.catchOp("block onPlace"); // Spigot +diff --git a/src/main/java/net/minecraft/world/level/portal/BlockPortalShape.java b/src/main/java/net/minecraft/world/level/portal/BlockPortalShape.java +index 9f7ff56e43a3dc0cc57ed3a2c6dbc729afafa1f8..3f8a674345bcad8289a48d2daa5e2a283528e952 100644 +--- a/src/main/java/net/minecraft/world/level/portal/BlockPortalShape.java ++++ b/src/main/java/net/minecraft/world/level/portal/BlockPortalShape.java +@@ -11,6 +11,7 @@ import net.minecraft.tags.Tag; + import net.minecraft.tags.TagsBlock; + import net.minecraft.util.MathHelper; + import net.minecraft.world.entity.EntitySize; ++import net.minecraft.world.item.context.ItemActionContext; + import net.minecraft.world.level.GeneratorAccess; + import net.minecraft.world.level.block.BlockPortal; + import net.minecraft.world.level.block.Blocks; +@@ -177,7 +178,10 @@ public class BlockPortalShape { + } + + // CraftBukkit start - return boolean +- public boolean createPortal() { ++ // Paper start - ItemActionContext param ++ @Deprecated public boolean createPortal() { return this.createPortal(null); } ++ public boolean createPortal(ItemActionContext itemActionContext) { ++ // Paper end + org.bukkit.World bworld = this.b.getMinecraftWorld().getWorld(); + + // Copy below for loop +@@ -189,7 +193,7 @@ public class BlockPortalShape { + blocks.add(state); + }); + +- PortalCreateEvent event = new PortalCreateEvent(blocks, bworld, null, PortalCreateEvent.CreateReason.FIRE); ++ PortalCreateEvent event = new PortalCreateEvent(blocks, bworld, itemActionContext == null || itemActionContext.getEntity() == null ? null : itemActionContext.getEntity().getBukkitEntity(), PortalCreateEvent.CreateReason.FIRE); // Paper - pass entity param + this.b.getMinecraftWorld().getMinecraftServer().server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { diff --git a/patches/server-unmapped/0001/0564-Fix-CraftTeam-null-check.patch b/patches/server-unmapped/0001/0564-Fix-CraftTeam-null-check.patch new file mode 100644 index 0000000000..52281e3135 --- /dev/null +++ b/patches/server-unmapped/0001/0564-Fix-CraftTeam-null-check.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: foss-mc <69294560+foss-mc@users.noreply.github.com> +Date: Sun, 30 Aug 2020 15:30:29 +0800 +Subject: [PATCH] Fix CraftTeam null check + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +index c631934fe9d205a06956c900d5b58a1d8a781c19..d637c2941ad0680299378b58ba0db7f7a6be07dc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +@@ -254,7 +254,7 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { + + @Override + public boolean hasEntry(String entry) throws IllegalArgumentException, IllegalStateException { +- Validate.notNull("Entry cannot be null"); ++ Validate.notNull(entry, "Entry cannot be null"); // Paper + + CraftScoreboard scoreboard = checkState(); + diff --git a/patches/server-unmapped/0001/0565-Add-more-Evoker-API.patch b/patches/server-unmapped/0001/0565-Add-more-Evoker-API.patch new file mode 100644 index 0000000000..ccde2ad0c9 --- /dev/null +++ b/patches/server-unmapped/0001/0565-Add-more-Evoker-API.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 23 Aug 2020 15:28:35 +0200 +Subject: [PATCH] Add more Evoker API + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityEvoker.java b/src/main/java/net/minecraft/world/entity/monster/EntityEvoker.java +index 64bfd84e8a5a0c2b1fec7044b688c7cc1083f18a..2aa6b6ca93c25c59ad224348aad1bb34d9bbc6a3 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityEvoker.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityEvoker.java +@@ -40,7 +40,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; + + public class EntityEvoker extends EntityIllagerWizard { + +- private EntitySheep bo; ++ private EntitySheep bo; public final EntitySheep getWololoTarget() { return this.bo; } public final void setWololoTarget(EntitySheep sheep) { this.bo = sheep; } // Paper - OBFHELPER + + public EntityEvoker(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -59,7 +59,7 @@ public class EntityEvoker extends EntityIllagerWizard { + this.goalSelector.a(8, new PathfinderGoalRandomStroll(this, 0.6D)); + this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 3.0F, 1.0F)); + this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F)); +- this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a()); ++ this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // Paper - decompile fix + this.targetSelector.a(2, (new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)).a(300)); + this.targetSelector.a(3, (new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)).a(300)); + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, false)); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java +index 5e8e9d95068342661a962aae9878a7eafaa06207..75a7832e9841921497a9a2d1cfd1b05807fe6ede 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit.entity; + ++import net.minecraft.world.entity.animal.EntitySheep; + import net.minecraft.world.entity.monster.EntityEvoker; + import net.minecraft.world.entity.monster.EntityIllagerWizard; + import org.bukkit.craftbukkit.CraftServer; +@@ -36,4 +37,17 @@ public class CraftEvoker extends CraftSpellcaster implements Evoker { + public void setCurrentSpell(Evoker.Spell spell) { + getHandle().setSpell(spell == null ? EntityIllagerWizard.Spell.NONE : EntityIllagerWizard.Spell.a(spell.ordinal())); + } ++ ++ // Paper start ++ @Override ++ public org.bukkit.entity.Sheep getWololoTarget() { ++ EntitySheep sheep = getHandle().getWololoTarget(); ++ return sheep == null ? null : (org.bukkit.entity.Sheep) sheep.getBukkitEntity(); ++ } ++ ++ @Override ++ public void setWololoTarget(org.bukkit.entity.Sheep sheep) { ++ getHandle().setWololoTarget(sheep == null ? null : ((org.bukkit.craftbukkit.entity.CraftSheep) sheep).getHandle()); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0566-Add-a-way-to-get-translation-keys-for-blocks-entitie.patch b/patches/server-unmapped/0001/0566-Add-a-way-to-get-translation-keys-for-blocks-entitie.patch new file mode 100644 index 0000000000..fa40bb9925 --- /dev/null +++ b/patches/server-unmapped/0001/0566-Add-a-way-to-get-translation-keys-for-blocks-entitie.patch @@ -0,0 +1,113 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MeFisto94 +Date: Tue, 11 Aug 2020 19:16:09 +0200 +Subject: [PATCH] Add a way to get translation keys for blocks, entities and + materials + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityTypes.java b/src/main/java/net/minecraft/world/entity/EntityTypes.java +index 9d2955f05aadd4bbc6dcfec068a55d7fe6950ba0..f2cf33d42839710a3bbdf0c8ea0be28af6fcb19d 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityTypes.java ++++ b/src/main/java/net/minecraft/world/entity/EntityTypes.java +@@ -278,6 +278,7 @@ public class EntityTypes { + return IRegistry.ENTITY_TYPE.getKey(entitytypes); + } + ++ public static Optional> getByName(String name) { return a(name); } // Paper - OBFHELPER + public static Optional> a(String s) { + return IRegistry.ENTITY_TYPE.getOptional(MinecraftKey.a(s)); + } +@@ -431,6 +432,7 @@ public class EntityTypes { + return this.bg; + } + ++ public String getDescriptionId() { return f(); } // Paper - OBFHELPER + public String f() { + if (this.bo == null) { + this.bo = SystemUtils.a("entity", IRegistry.ENTITY_TYPE.getKey(this)); +diff --git a/src/main/java/net/minecraft/world/item/Item.java b/src/main/java/net/minecraft/world/item/Item.java +index ca513e7b0a416860aba89e41de6a7c5ff42baa69..5d7c44a53fb98532057b09176677ce0d719b055b 100644 +--- a/src/main/java/net/minecraft/world/item/Item.java ++++ b/src/main/java/net/minecraft/world/item/Item.java +@@ -56,7 +56,7 @@ public class Item implements IMaterial { + private final FoodInfo foodInfo; + + public static int getId(Item item) { +- return item == null ? 0 : IRegistry.ITEM.a((Object) item); ++ return item == null ? 0 : IRegistry.ITEM.a(item); // Paper - Fix Decompiler Issue + } + + public static Item getById(int i) { +@@ -152,6 +152,7 @@ public class Item implements IMaterial { + return IRegistry.ITEM.getKey(this).getKey(); + } + ++ public String getOrCreateDescriptionId() { return m(); } // Paper - OBFHELPER + protected String m() { + if (this.name == null) { + this.name = SystemUtils.a("item", IRegistry.ITEM.getKey(this)); +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 cc512bd2e89382e7fdbc59b41640e95ccafbbfe9..768934fa4158a9773d06f5b23bfb19db75f6d179 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -318,6 +318,7 @@ public class Block extends BlockBase implements IMaterial { + return !this.material.isBuildable() && !this.material.isLiquid(); + } + ++ public String getDescriptionId() { return i(); } // Paper - OBFHELPER + public String i() { + if (this.name == null) { + this.name = SystemUtils.a("block", IRegistry.BLOCK.getKey(this)); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index e3ab0b76e5003553b29215a43bc5a762f2663648..ee8977a1e4a83598ba7873c4c482fea828c6b26c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -764,5 +764,10 @@ public class CraftBlock implements Block { + public com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup() { + return new com.destroystokyo.paper.block.CraftBlockSoundGroup(getNMSBlock().getBlockData().getStepSound()); + } ++ ++ @Override ++ public String getTranslationKey() { ++ return org.bukkit.Bukkit.getUnsafe().getTranslationKey(this); ++ } + // 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 2519dbce9717ff647d50c31aed6aca68b9f4e3af..6c59816bb9246f22d518aa9f87ec382ae3d3a014 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -45,6 +45,7 @@ import org.bukkit.Registry; + import org.bukkit.UnsafeValues; + import org.bukkit.advancement.Advancement; + import org.bukkit.block.data.BlockData; ++import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.craftbukkit.block.data.CraftBlockData; + import org.bukkit.craftbukkit.inventory.CraftItemStack; + import org.bukkit.craftbukkit.legacy.CraftLegacy; +@@ -420,6 +421,25 @@ public final class CraftMagicNumbers implements UnsafeValues { + throw new RuntimeException(); + } + } ++ ++ @Override ++ public String getTranslationKey(Material mat) { ++ if (mat.isBlock()) { ++ return getBlock(mat).getDescriptionId(); ++ } ++ return getItem(mat).getOrCreateDescriptionId(); ++ } ++ ++ @Override ++ public String getTranslationKey(org.bukkit.block.Block block) { ++ return ((org.bukkit.craftbukkit.block.CraftBlock)block).getNMS().getBlock().getDescriptionId(); ++ } ++ ++ @Override ++ public String getTranslationKey(org.bukkit.entity.EntityType type) { ++ return net.minecraft.world.entity.EntityTypes.getByName(type.getName()).map(net.minecraft.world.entity.EntityTypes::getDescriptionId).orElse(null); ++ } ++ + // Paper end + + /** diff --git a/patches/server-unmapped/0001/0567-Create-HoverEvent-from-ItemStack-Entity.patch b/patches/server-unmapped/0001/0567-Create-HoverEvent-from-ItemStack-Entity.patch new file mode 100644 index 0000000000..d504fb12a8 --- /dev/null +++ b/patches/server-unmapped/0001/0567-Create-HoverEvent-from-ItemStack-Entity.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ysl3000 +Date: Mon, 6 Jul 2020 22:18:04 +0200 +Subject: [PATCH] Create HoverEvent from ItemStack Entity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index e6c818d32713d9fb0f02a46696bd8a5dabe2a3ae..6966b9d1ce674232d3f867798fa58bd0933ff69e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -360,5 +360,40 @@ public final class CraftItemFactory implements ItemFactory { + + return nms != null ? net.minecraft.locale.LocaleLanguage.getInstance().translateKey(nms.getItem().getName()) : null; + } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(ItemStack itemStack) { ++ net.md_5.bungee.api.chat.ItemTag itemTag = net.md_5.bungee.api.chat.ItemTag.ofNbt(CraftItemStack.asNMSCopy(itemStack).getOrCreateTag().toString()); ++ return new net.md_5.bungee.api.chat.hover.content.Item( ++ itemStack.getType().getKey().toString(), ++ itemStack.getAmount(), ++ itemTag); ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity) { ++ return hoverContentOf(entity, org.apache.commons.lang3.StringUtils.isBlank(entity.getCustomName()) ? null : new net.md_5.bungee.api.chat.TextComponent(entity.getCustomName())); ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, String customName) { ++ return hoverContentOf(entity, org.apache.commons.lang3.StringUtils.isBlank(customName) ? null : new net.md_5.bungee.api.chat.TextComponent(customName)); ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, net.md_5.bungee.api.chat.BaseComponent customName) { ++ return new net.md_5.bungee.api.chat.hover.content.Entity( ++ entity.getType().getKey().toString(), ++ entity.getUniqueId().toString(), ++ customName); ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, net.md_5.bungee.api.chat.BaseComponent[] customName) { ++ return new net.md_5.bungee.api.chat.hover.content.Entity( ++ entity.getType().getKey().toString(), ++ entity.getUniqueId().toString(), ++ new net.md_5.bungee.api.chat.TextComponent(customName)); ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0568-Cache-block-data-strings.patch b/patches/server-unmapped/0001/0568-Cache-block-data-strings.patch new file mode 100644 index 0000000000..dfbe6fe8ef --- /dev/null +++ b/patches/server-unmapped/0001/0568-Cache-block-data-strings.patch @@ -0,0 +1,70 @@ +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 483abe5b3c0c00ebdea405e9bb24509f743bfc2c..ef980b8cba0e30fc65b119d08a034ced7fdb2bc8 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1953,6 +1953,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant%s", nms, bukkit); + } + ++ // Paper start - cache block data strings ++ private static Map stringDataCache = new HashMap<>(); ++ ++ 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.REGISTRY_ID.forEach(blockData -> stringDataCache.put(blockData.toString(), blockData.createCraftBlockData())); ++ } ++ // Paper end ++ + 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 = CraftMagicNumbers.getBlock(material); ++ if (block != null) { ++ MinecraftKey key = IRegistry.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 + IBlockData blockData; + Block block = CraftMagicNumbers.getBlock(material); + Map, Comparable> parsed = null; diff --git a/patches/server-unmapped/0001/0569-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/patches/server-unmapped/0001/0569-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch new file mode 100644 index 0000000000..b9cb472b5a --- /dev/null +++ b/patches/server-unmapped/0001/0569-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch @@ -0,0 +1,71 @@ +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/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 1d154ef60fd979340cf925748251669e860d4094..68f8a7f227c4d683d4c13d634ccfbabe759e82b3 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -693,7 +693,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + public void a(PacketPlayInTeleportAccept packetplayinteleportaccept) { + PlayerConnectionUtils.ensureMainThread(packetplayinteleportaccept, this, this.player.getWorldServer()); + if (packetplayinteleportaccept.b() == this.teleportAwait && this.teleportPos != null) { // CraftBukkit +- this.player.setLocation(this.teleportPos.x, this.teleportPos.y, this.teleportPos.z, this.player.yaw, this.player.pitch); ++ this.player.setPositionRotation(this.teleportPos.x, this.teleportPos.y, this.teleportPos.z, this.player.yaw, this.player.pitch); // Paper - use proper setPositionRotation for teleportation + this.o = this.teleportPos.x; + this.p = this.teleportPos.y; + this.q = this.teleportPos.z; +@@ -1537,7 +1537,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + // CraftBukkit end + + this.A = this.e; +- this.player.setLocation(d0, d1, d2, f, f1); ++ this.player.setPositionRotation(d0, d1, d2, f, f1); // Paper - use proper setPositionRotation for teleportation + this.player.forceCheckHighPriority(); // Paper + this.player.playerConnection.sendPacket(new PacketPlayOutPosition(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.teleportAwait)); + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 743d4725c0a26a8abd0a98eed2ec45ffba6211ad..344862c3f479ae7b6d4f929c9ef7882aba983ffb 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -145,6 +145,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; ++ boolean preserveMotion = true; // Paper - keep initial motion on first setPositionRotation + static boolean isLevelAtLeast(NBTTagCompound tag, int level) { + return tag.hasKey("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; + } +@@ -1408,6 +1409,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public void setPositionRotation(double d0, double d1, double d2, float f, float f1) { ++ // Paper - cancel entity velocity if teleported ++ if (!preserveMotion) { ++ this.mot = Vec3D.ORIGIN; ++ } else { ++ this.preserveMotion = false; ++ } ++ // Paper end + this.g(d0, d1, d2); + this.yaw = f; + this.pitch = f1; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 387f6f6fa9bbb1cce544cfb907f68c7993752dd7..fb52a60437d48282f3e99e7eccb0e76b446155f9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -558,7 +558,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + // entity.setLocation() throws no event, and so cannot be cancelled +- entity.setLocation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); ++ entity.setPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); // Paper - use proper setPosition, as per vanilla teleporting + // SPIGOT-619: Force sync head rotation also + entity.setHeadRotation(location.getYaw()); + ((net.minecraft.server.level.WorldServer) entity.world).chunkCheck(entity); // Spigot - register to new chunk diff --git a/patches/server-unmapped/0001/0570-Add-additional-open-container-api-to-HumanEntity.patch b/patches/server-unmapped/0001/0570-Add-additional-open-container-api-to-HumanEntity.patch new file mode 100644 index 0000000000..775318f266 --- /dev/null +++ b/patches/server-unmapped/0001/0570-Add-additional-open-container-api-to-HumanEntity.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Wed, 26 Aug 2020 02:12:31 -0400 +Subject: [PATCH] Add additional open container api to HumanEntity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 92501a415813b3b0f2be492a4711962320264a76..b99423b3b413fc6364c6530a99e3c74dd406e1b4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -458,6 +458,70 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + return this.getHandle().activeContainer.getBukkitView(); + } + ++ // Paper start - Add additional containers ++ @Override ++ public InventoryView openAnvil(Location location, boolean force) { ++ return openInventory(location, force, Material.ANVIL); ++ } ++ ++ @Override ++ public InventoryView openCartographyTable(Location location, boolean force) { ++ return openInventory(location, force, Material.CARTOGRAPHY_TABLE); ++ } ++ ++ @Override ++ public InventoryView openGrindstone(Location location, boolean force) { ++ return openInventory(location, force, Material.GRINDSTONE); ++ } ++ ++ @Override ++ public InventoryView openLoom(Location location, boolean force) { ++ return openInventory(location, force, Material.LOOM); ++ } ++ ++ @Override ++ public InventoryView openSmithingTable(Location location, boolean force) { ++ return openInventory(location, force, Material.SMITHING_TABLE); ++ } ++ ++ @Override ++ public InventoryView openStonecutter(Location location, boolean force) { ++ return openInventory(location, force, Material.STONECUTTER); ++ } ++ ++ private InventoryView openInventory(Location location, boolean force, Material material) { ++ org.spigotmc.AsyncCatcher.catchOp("open" + material); ++ if (location == null) { ++ location = getLocation(); ++ } ++ if (!force) { ++ Block block = location.getBlock(); ++ if (block.getType() != material) { ++ return null; ++ } ++ } ++ net.minecraft.world.level.block.Block block; ++ if (material == Material.ANVIL) { ++ block = Blocks.ANVIL; ++ } else if (material == Material.CARTOGRAPHY_TABLE) { ++ block = Blocks.CARTOGRAPHY_TABLE; ++ } else if (material == Material.GRINDSTONE) { ++ block = Blocks.GRINDSTONE; ++ } else if (material == Material.LOOM) { ++ block = Blocks.LOOM; ++ } else if (material == Material.SMITHING_TABLE) { ++ block = Blocks.SMITHING_TABLE; ++ } else if (material == Material.STONECUTTER) { ++ block = Blocks.STONECUTTER; ++ } else { ++ throw new IllegalArgumentException("Unsupported inventory type: " + material); ++ } ++ getHandle().openContainer(block.getInventory(null, getHandle().world, new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ()))); ++ getHandle().activeContainer.checkReachable = !force; ++ return getHandle().activeContainer.getBukkitView(); ++ } ++ // Paper end ++ + @Override + public void closeInventory() { + // Paper start diff --git a/patches/server-unmapped/0001/0571-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch b/patches/server-unmapped/0001/0571-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch new file mode 100644 index 0000000000..a02addc375 --- /dev/null +++ b/patches/server-unmapped/0001/0571-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 12 Sep 2020 17:21:38 -0400 +Subject: [PATCH] Cache DataFixerUpper Rewrite Rules on demand + +Mojang precaches every single potential rewrite rule that could ever +exist on server startup. This includes rules from all the way back to versions from 6+ years ago. + +This is the source of why the server hogs every CPU core at 100% every start. + +For anyone who hard resets for updates or has force upgraded their entire world, this +results in completely wasted cpu cycles. + +This massive CPU usage also delays server startup time. + +We improve this by making "min version to precache" that defaults to a future version +so that no rewrite rules are precached. + +someone who expects to be converting a lot chunks could theoretically set +-DPaper.minPrecachedDatafixVersion= as a startup +parameter and only build from that point on. + +However this will likely never be needed as the server will still run +the same cache logic on demand when it's actually needed. The only +cost would be some delay on the FIRST chunk conversion, but paper already +runs chunk conversions on another thread so this will likely never be +a concern for TPS. + +This patch will significantly reduce CPU use on startup, reduce memory usage, +and improve server startup time. + +diff --git a/src/main/java/com/mojang/datafixers/DataFixerBuilder.java b/src/main/java/com/mojang/datafixers/DataFixerBuilder.java +index edb77982d273e9492ab1a669ca1ad89da2ec3c3e..abc265b00044b14abb55c2628d454ee01fef467b 100644 +--- a/src/main/java/com/mojang/datafixers/DataFixerBuilder.java ++++ b/src/main/java/com/mojang/datafixers/DataFixerBuilder.java +@@ -26,8 +26,10 @@ public class DataFixerBuilder { + private final Int2ObjectSortedMap schemas = new Int2ObjectAVLTreeMap<>(); + private final List globalList = Lists.newArrayList(); + private final IntSortedSet fixerVersions = new IntAVLTreeSet(); ++ private final int minDataFixPrecacheVersion; // Paper + + public DataFixerBuilder(final int dataVersion) { ++ minDataFixPrecacheVersion = Integer.getInteger("Paper.minPrecachedDatafixVersion", dataVersion+1) * 10; // Paper - default to precache nothing - mojang stores versions * 10 to allow for 'sub versions' + this.dataVersion = dataVersion; + } + +@@ -65,6 +67,7 @@ public class DataFixerBuilder { + final IntBidirectionalIterator iterator = fixerUpper.fixerVersions().iterator(); + while (iterator.hasNext()) { + final int versionKey = iterator.nextInt(); ++ if (versionKey < minDataFixPrecacheVersion) continue; // Paper + final Schema schema = schemas.get(versionKey); + for (final String typeName : schema.types()) { + CompletableFuture.runAsync(() -> { diff --git a/patches/server-unmapped/0001/0572-Extend-block-drop-capture-to-capture-all-items-added.patch b/patches/server-unmapped/0001/0572-Extend-block-drop-capture-to-capture-all-items-added.patch new file mode 100644 index 0000000000..8def13de1d --- /dev/null +++ b/patches/server-unmapped/0001/0572-Extend-block-drop-capture-to-capture-all-items-added.patch @@ -0,0 +1,60 @@ +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/PlayerInteractManager.java b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +index 192a1c894a371ecab68e5e8ec10fab62268d1a9c..d0a4b5cc21d40f17ed85cc174797e2705d7a0dc3 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerInteractManager.java ++++ b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +@@ -12,6 +12,7 @@ import net.minecraft.world.EnumHand; + import net.minecraft.world.EnumInteractionResult; + import net.minecraft.world.ITileInventory; + import net.minecraft.world.InteractionResultWrapper; ++import net.minecraft.world.entity.item.EntityItem; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.context.ItemActionContext; +@@ -418,10 +419,12 @@ public class PlayerInteractManager { + // return true; // CraftBukkit + } + // CraftBukkit start ++ java.util.List itemsToDrop = world.captureDrops; // Paper - store current list ++ world.captureDrops = null; // Paper - Remove this earlier so that we can actually drop stuff + if (event.isDropItems()) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, world.captureDrops); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - use stored ref + } +- world.captureDrops = null; ++ //world.captureDrops = null; // Paper - move up + + // Drop event experience + if (flag && event != null) { +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 246708808cbcd9a5c1b8690c869a514d02fddb5c..4a87b9ebc2a584d8a2fca874342057e81fbbc1c6 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -99,6 +99,7 @@ import net.minecraft.world.entity.animal.EntityWaterAnimal; + import net.minecraft.world.entity.animal.horse.EntityHorseSkeleton; + import net.minecraft.world.entity.boss.EntityComplexPart; + import net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon; ++import net.minecraft.world.entity.item.EntityItem; + import net.minecraft.world.entity.npc.NPC; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.entity.raid.PersistentRaid; +@@ -1290,6 +1291,13 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } else if (this.isUUIDTaken(entity)) { + return false; + } else { ++ // Paper start - capture all item additions to the world ++ if (captureDrops != null && entity instanceof EntityItem) { ++ captureDrops.add((EntityItem) entity); ++ return true; ++ } ++ // Paper end ++ + if (!CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) { + return false; + } diff --git a/patches/server-unmapped/0001/0573-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch b/patches/server-unmapped/0001/0573-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch new file mode 100644 index 0000000000..974a34ff2f --- /dev/null +++ b/patches/server-unmapped/0001/0573-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sun, 27 Sep 2020 16:25:24 +0200 +Subject: [PATCH] Don't mark dirty in invalid locations (SPIGOT-6086) + + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java +index 00cebd33101916f29bbc192d531ac0fba31e037b..a323b76f68c273a73cb3f20167a668b2100f4944 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java +@@ -389,6 +389,7 @@ public class PlayerChunk { + } + + public void a(BlockPosition blockposition) { ++ if (!blockposition.isValidLocation()) return; // Paper - SPIGOT-6086 for all invalid locations; avoid acquiring locks + Chunk chunk = this.getSendingChunk(); // Paper - no-tick view distance + + if (chunk != null) { diff --git a/patches/server-unmapped/0001/0574-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch b/patches/server-unmapped/0001/0574-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch new file mode 100644 index 0000000000..5b411c4c2b --- /dev/null +++ b/patches/server-unmapped/0001/0574-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch @@ -0,0 +1,37 @@ +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 344862c3f479ae7b6d4f929c9ef7882aba983ffb..e2301dbeb3d76684b2a0ab4262bb76cab3557789 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3473,4 +3473,10 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + void accept(Entity entity, double d0, double d1, double d2); + } ++ ++ // Paper start ++ public static int nextEntityId() { ++ return entityCount.incrementAndGet(); ++ } ++ // 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 6c59816bb9246f22d518aa9f87ec382ae3d3a014..a1c918e84627d79f6665237851f614880a9da6f7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -440,6 +440,10 @@ public final class CraftMagicNumbers implements UnsafeValues { + return net.minecraft.world.entity.EntityTypes.getByName(type.getName()).map(net.minecraft.world.entity.EntityTypes::getDescriptionId).orElse(null); + } + ++ public int nextEntityId() { ++ return net.minecraft.world.entity.Entity.nextEntityId(); ++ } ++ + // Paper end + + /** diff --git a/patches/server-unmapped/0001/0575-Lazily-track-plugin-scoreboards-by-default.patch b/patches/server-unmapped/0001/0575-Lazily-track-plugin-scoreboards-by-default.patch new file mode 100644 index 0000000000..974e02ca68 --- /dev/null +++ b/patches/server-unmapped/0001/0575-Lazily-track-plugin-scoreboards-by-default.patch @@ -0,0 +1,102 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Sat, 3 Oct 2020 04:15:09 -0400 +Subject: [PATCH] Lazily track plugin scoreboards by default + +On servers with plugins that constantly churn through scoreboards, there is a risk of +degraded GC performance due to the number of scoreboards held on by weak references. +Most plugins don't even need the (vanilla) functionality that requires all plugin +scoreboards to be tracked by the server. Instead, only track scoreboards when an +objective is added with a non-dummy criteria. + +This is a breaking change, however the change is a much more sensible default. In case +this breaks your workflow you can always force all scoreboards to be tracked with +settings.track-plugin-scoreboards in paper.yml. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 545948f20efd6c8dd42140b565af94cd6b52b661..7d50aded88f5b7dfebaea1aebc86231f7b5c4e25 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -462,4 +462,9 @@ public class PaperConfig { + private static void maxJoinsPerTick() { + maxJoinsPerTick = getInt("settings.max-joins-per-tick", 3); + } ++ ++ public static boolean trackPluginScoreboards; ++ private static void trackPluginScoreboards() { ++ trackPluginScoreboards = getBoolean("settings.track-plugin-scoreboards", false); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +index 6ea491f6308317059c4bc6735abbdce370df0f34..3dfb6ad1ed5fae773b88cbe9ad540fd2520b8b50 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +@@ -19,6 +19,7 @@ import org.bukkit.scoreboard.Team; + + public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + final Scoreboard board; ++ boolean registeredGlobally = false; // Paper + + CraftScoreboard(Scoreboard board) { + this.board = board; +@@ -45,6 +46,12 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + Validate.isTrue(name.length() <= 16, "The name '" + name + "' is longer than the limit of 16 characters"); + Validate.isTrue(board.getObjective(name) == null, "An objective of name '" + name + "' already exists"); + CraftCriteria craftCriteria = CraftCriteria.getFromBukkit(criteria); ++ // Paper start - the block comment from the old registerNewObjective didnt cause a conflict when rebasing, so this block wasn't added to the adventure registerNewObjective ++ if (craftCriteria.criteria != net.minecraft.world.scores.criteria.IScoreboardCriteria.DUMMY && !registeredGlobally) { ++ net.minecraft.server.MinecraftServer.getServer().server.getScoreboardManager().registerScoreboardForVanilla(this); ++ registeredGlobally = true; ++ } ++ // Paper end + ScoreboardObjective objective = board.registerObjective(name, craftCriteria.criteria, io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName), CraftScoreboardTranslations.fromBukkitRender(renderType)); + return new CraftObjective(this, objective); + } +@@ -66,6 +73,12 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + Validate.isTrue(board.getObjective(name) == null, "An objective of name '" + name + "' already exists"); + + CraftCriteria craftCriteria = CraftCriteria.getFromBukkit(criteria); ++ // Paper start ++ if (craftCriteria.criteria != net.minecraft.server.IScoreboardCriteria.DUMMY && !registeredGlobally) { ++ net.minecraft.server.MinecraftServer.getServer().server.getScoreboardManager().registerScoreboardForVanilla(this); ++ registeredGlobally = true; ++ } ++ // Paper end + ScoreboardObjective objective = board.registerObjective(name, craftCriteria.criteria, CraftChatMessage.fromStringOrNull(displayName), CraftScoreboardTranslations.fromBukkitRender(renderType)); + return new CraftObjective(this, objective);*/ // Paper + return registerNewObjective(name, criteria, io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(displayName), renderType); // Paper +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +index 73663bcea163cda26f531902141a9ad3ad7eba57..9c30a79cc55c6455aa18e3798728deaacc3434ca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +@@ -30,6 +30,7 @@ public final class CraftScoreboardManager implements ScoreboardManager { + + public CraftScoreboardManager(MinecraftServer minecraftserver, net.minecraft.world.scores.Scoreboard scoreboardServer) { + mainScoreboard = new CraftScoreboard(scoreboardServer); ++ mainScoreboard.registeredGlobally = true; // Paper + server = minecraftserver; + scoreboards.add(mainScoreboard); + } +@@ -43,10 +44,22 @@ public final class CraftScoreboardManager implements ScoreboardManager { + public CraftScoreboard getNewScoreboard() { + org.spigotmc.AsyncCatcher.catchOp("scoreboard creation"); // Spigot + CraftScoreboard scoreboard = new CraftScoreboard(new ScoreboardServer(server)); ++ // Paper start ++ if (com.destroystokyo.paper.PaperConfig.trackPluginScoreboards) { ++ scoreboard.registeredGlobally = true; + scoreboards.add(scoreboard); ++ } ++ // Paper end + return scoreboard; + } + ++ // Paper start ++ public void registerScoreboardForVanilla(CraftScoreboard scoreboard) { ++ org.spigotmc.AsyncCatcher.catchOp("scoreboard registration"); ++ scoreboards.add(scoreboard); ++ } ++ // Paper end ++ + // CraftBukkit method + public CraftScoreboard getPlayerBoard(CraftPlayer player) { + CraftScoreboard board = playerBoards.get(player); diff --git a/patches/server-unmapped/0001/0576-Entity-isTicking.patch b/patches/server-unmapped/0001/0576-Entity-isTicking.patch new file mode 100644 index 0000000000..2842cb3c9f --- /dev/null +++ b/patches/server-unmapped/0001/0576-Entity-isTicking.patch @@ -0,0 +1,42 @@ +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 e2301dbeb3d76684b2a0ab4262bb76cab3557789..f1fa11217ab09133f4f19f5c73dfdab2f2b434e2 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -48,6 +48,7 @@ import net.minecraft.resources.MinecraftKey; + import net.minecraft.resources.ResourceKey; + import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkProviderServer; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.server.level.PlayerChunkMap; + import net.minecraft.server.level.TicketType; +@@ -3478,5 +3479,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + public static int nextEntityId() { + return entityCount.incrementAndGet(); + } ++ ++ public boolean isTicking() { ++ return ((ChunkProviderServer) world.getChunkProvider()).isInEntityTickingChunk(this); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index fb52a60437d48282f3e99e7eccb0e76b446155f9..295ffab08672d77d88aca368cb5b56f80bc4f1b5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1161,5 +1161,9 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public boolean isInLava() { + return getHandle().isInLava(); + } ++ ++ public boolean isTicking() { ++ return getHandle().isTicking(); ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0577-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch b/patches/server-unmapped/0001/0577-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch new file mode 100644 index 0000000000..e48f7dcbc2 --- /dev/null +++ b/patches/server-unmapped/0001/0577-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch @@ -0,0 +1,19 @@ +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 ef980b8cba0e30fc65b119d08a034ced7fdb2bc8..43b713476c6f841aafaac9f2a1216b937261efc2 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2018,6 +2018,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant list = Lists.newArrayList(playerlist.getPlayers()); + Iterator iterator = list.iterator(); + diff --git a/patches/server-unmapped/0001/0578-Fix-Not-a-string-Map-Conversion-spam.patch b/patches/server-unmapped/0001/0578-Fix-Not-a-string-Map-Conversion-spam.patch new file mode 100644 index 0000000000..26697d6feb --- /dev/null +++ b/patches/server-unmapped/0001/0578-Fix-Not-a-string-Map-Conversion-spam.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 8 Oct 2020 00:00:25 -0400 +Subject: [PATCH] Fix "Not a string" Map Conversion spam + +The maps did convert successfully, but had noisy logs due to Spigot +implementing this logic incorrectly. + +This stops the spam by converting the old format to new before +requesting the world. + +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java b/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java +index d470af1814af332595c1c0beb1cdc552e186a6bb..1f9b710f2af3c5067b3c2b73bebb11f8e22c403e 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/WorldMap.java +@@ -11,8 +11,10 @@ import javax.annotation.Nullable; + import net.minecraft.core.BlockPosition; + import net.minecraft.nbt.DynamicOpsNBT; + import net.minecraft.nbt.NBTBase; ++import net.minecraft.nbt.NBTNumber; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.nbt.NBTTagList; ++import net.minecraft.nbt.NBTTagString; + import net.minecraft.network.chat.IChatBaseComponent; + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.PacketPlayOutMap; +@@ -94,7 +96,26 @@ public class WorldMap extends PersistentBase { + + @Override + public void a(NBTTagCompound nbttagcompound) { +- DataResult> dataresult = DimensionManager.a(new Dynamic(DynamicOpsNBT.a, nbttagcompound.get("dimension"))); // CraftBukkit - decompile error ++ // Paper start - fix "Not a string" spam ++ NBTBase dimension = nbttagcompound.get("dimension"); ++ if (dimension instanceof NBTNumber && ((NBTNumber) dimension).asInt() >= CraftWorld.CUSTOM_DIMENSION_OFFSET) { ++ long least = nbttagcompound.getLong("UUIDLeast"); ++ long most = nbttagcompound.getLong("UUIDMost"); ++ ++ if (least != 0L && most != 0L) { ++ this.uniqueId = new UUID(most, least); ++ CraftWorld world = (CraftWorld) server.getWorld(this.uniqueId); ++ if (world != null) { ++ dimension = NBTTagString.create("minecraft:" + world.getName().toLowerCase(java.util.Locale.ENGLISH)); ++ } else { ++ dimension = NBTTagString.create("bukkit:_invalidworld_"); ++ } ++ } else { ++ dimension = NBTTagString.create("bukkit:_invalidworld_"); ++ } ++ } ++ DataResult> dataresult = DimensionManager.a(new Dynamic(DynamicOpsNBT.a, dimension)); // CraftBukkit - decompile error ++ // Paper end - fix "Not a string" spam + Logger logger = WorldMap.LOGGER; + + logger.getClass(); diff --git a/patches/server-unmapped/0001/0579-Fix-CME-on-adding-a-passenger-in-CreatureSpawnEvent.patch b/patches/server-unmapped/0001/0579-Fix-CME-on-adding-a-passenger-in-CreatureSpawnEvent.patch new file mode 100644 index 0000000000..48e233764b --- /dev/null +++ b/patches/server-unmapped/0001/0579-Fix-CME-on-adding-a-passenger-in-CreatureSpawnEvent.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Sun, 4 Oct 2020 19:55:25 -0700 +Subject: [PATCH] Fix CME on adding a passenger in CreatureSpawnEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f1fa11217ab09133f4f19f5c73dfdab2f2b434e2..2a3025793db52a18e58f832c9da78c6c3b39d33c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3177,7 +3177,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public Stream recursiveStream() { +- return Stream.concat(Stream.of(this), this.passengers.stream().flatMap(Entity::recursiveStream)); ++ return Stream.concat(Stream.of(this), com.google.common.collect.ImmutableList.copyOf(this.passengers).stream().flatMap(Entity::recursiveStream)); // Paper + } + + public boolean hasSinglePlayerPassenger() { diff --git a/patches/server-unmapped/0001/0580-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch b/patches/server-unmapped/0001/0580-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch new file mode 100644 index 0000000000..42d7399da7 --- /dev/null +++ b/patches/server-unmapped/0001/0580-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Wed, 18 Mar 2020 00:07:46 -0500 +Subject: [PATCH] MC-147729: Drop items that are extra from a crafting recipe + + +diff --git a/src/main/java/net/minecraft/recipebook/AutoRecipe.java b/src/main/java/net/minecraft/recipebook/AutoRecipe.java +index 897f7270bae601b39d74d6a56a60f0ac7f1f6090..3bcd2c5a5352b8fb92d09b0a6914a1013569c8d6 100644 +--- a/src/main/java/net/minecraft/recipebook/AutoRecipe.java ++++ b/src/main/java/net/minecraft/recipebook/AutoRecipe.java +@@ -71,7 +71,12 @@ public class AutoRecipe implements AutoRecipeAbstract +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/EnderDragonBattle.java b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +index 67077351e6e85ade753b960704743fa0fd9a158d..adfd36d9e62828c148726f0f26dfb1825e32abf1 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +@@ -445,6 +445,7 @@ public class EnderDragonBattle { + entityenderdragon.setPositionRotation(0.0D, 128.0D, 0.0D, this.world.random.nextFloat() * 360.0F, 0.0F); + this.world.addEntity(entityenderdragon); + this.dragonUUID = entityenderdragon.getUniqueID(); ++ this.resetCrystals(); // Paper + return entityenderdragon; + } + diff --git a/patches/server-unmapped/0001/0582-Fix-for-large-move-vectors-crashing-server.patch b/patches/server-unmapped/0001/0582-Fix-for-large-move-vectors-crashing-server.patch new file mode 100644 index 0000000000..03994e2ff1 --- /dev/null +++ b/patches/server-unmapped/0001/0582-Fix-for-large-move-vectors-crashing-server.patch @@ -0,0 +1,66 @@ +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/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 68f8a7f227c4d683d4c13d634ccfbabe759e82b3..8ffc98faecca616005fc56031718a7a10de40102 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -518,19 +518,24 @@ public class PlayerConnection implements PacketListenerPlayIn { + + if (entity != this.player && entity.getRidingPassenger() == this.player && entity == this.r) { + WorldServer worldserver = this.player.getWorldServer(); +- double d0 = entity.locX(); +- double d1 = entity.locY(); +- double d2 = entity.locZ(); +- double d3 = packetplayinvehiclemove.getX(); +- double d4 = packetplayinvehiclemove.getY(); +- double d5 = packetplayinvehiclemove.getZ(); ++ double d0 = entity.locX();double fromX = d0; // Paper - OBFHELPER ++ double d1 = entity.locY();double fromY = d1; // Paper - OBFHELPER ++ double d2 = entity.locZ();double fromZ = d2; // Paper - OBFHELPER ++ double d3 = packetplayinvehiclemove.getX();double toX = d3; // Paper - OBFHELPER ++ double d4 = packetplayinvehiclemove.getY();double toY = d4; // Paper - OBFHELPER ++ double d5 = packetplayinvehiclemove.getZ();double toZ = d5; // Paper - OBFHELPER + float f = packetplayinvehiclemove.getYaw(); + float f1 = packetplayinvehiclemove.getPitch(); + double d6 = d3 - this.s; + double d7 = d4 - this.t; + double d8 = d5 - this.u; + double d9 = entity.getMot().g(); +- 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); ++ // Paper end - fix large move vectors killing the server + + + // CraftBukkit start - handle custom speeds and skipped ticks +@@ -1232,7 +1237,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + double d2 = this.player.locZ(); + double d3 = this.player.locY(); + double d4 = packetplayinflying.a(this.player.locX());double toX = d4; // Paper - OBFHELPER +- double d5 = packetplayinflying.b(this.player.locY()); ++ double d5 = packetplayinflying.b(this.player.locY());double toY = d5; // Paper - OBFHELPER + double d6 = packetplayinflying.c(this.player.locZ());double toZ = d6; // Paper - OBFHELPER + float f = packetplayinflying.a(this.player.yaw); + float f1 = packetplayinflying.b(this.player.pitch); +@@ -1240,7 +1245,12 @@ public class PlayerConnection implements PacketListenerPlayIn { + double d8 = d5 - this.m; + double d9 = d6 - this.n; + double d10 = this.player.getMot().g(); +- double d11 = d7 * d7 + d8 * d8 + d9 * d9; ++ // Paper start - fix large move vectors killing the server ++ double currDeltaX = toX - prevX; ++ double currDeltaY = toY - prevY; ++ double currDeltaZ = toZ - prevZ; ++ double d11 = Math.max(d7 * d7 + d8 * d8 + d9 * d9, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ // Paper end - fix large move vectors killing the server + + if (this.player.isSleeping()) { + if (d11 > 1.0D) { diff --git a/patches/server-unmapped/0001/0583-Optimise-getType-calls.patch b/patches/server-unmapped/0001/0583-Optimise-getType-calls.patch new file mode 100644 index 0000000000..16993ab18d --- /dev/null +++ b/patches/server-unmapped/0001/0583-Optimise-getType-calls.patch @@ -0,0 +1,96 @@ +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/IBlockData.java b/src/main/java/net/minecraft/world/level/block/state/IBlockData.java +index 9e1ebfd7befe9e2fc3396b4dcd5e8fc7c23ee305..ed28710fefee4fad96290b2d166ac9dfe3d1fa6e 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/IBlockData.java ++++ b/src/main/java/net/minecraft/world/level/block/state/IBlockData.java +@@ -11,6 +11,19 @@ public class IBlockData extends BlockBase.BlockData { + + public static final Codec b = a((Codec) IRegistry.BLOCK, Block::getBlockData).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.util.CraftMagicNumbers.getMaterial(this.getBlock()); ++ } ++ ++ return this.cachedMaterial; ++ } ++ // Paper end - optimise getType calls ++ + public IBlockData(Block block, ImmutableMap, Comparable> immutablemap, MapCodec mapcodec) { + super(block, immutablemap, mapcodec); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +index aa20cecc93ac53ce7f1b956ea5065ae80b55cff5..fc65c138bab242d5b533381ea496a2a48fcd91ea 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +@@ -79,7 +79,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { + public Material getBlockType(int x, int y, int z) { + CraftChunk.validateChunkCoordinates(x, y, z); + +- return CraftMagicNumbers.getMaterial(blockids[y >> 4].a(x, y & 0xF, z).getBlock()); ++ return blockids[y >> 4].a(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 ee8977a1e4a83598ba7873c4c482fea828c6b26c..f1f03bd0162a158761f300cbd96aa32101542abb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -214,7 +214,7 @@ public class CraftBlock implements Block { + + @Override + public Material getType() { +- return CraftMagicNumbers.getMaterial(world.getType(position).getBlock()); ++ return world.getType(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 b7ce677572f1f5ed01f3a8caf9368222e03bb69c..1960f01b5e4d7bff96d466e9a48f1d5300f261d1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -135,7 +135,7 @@ public class CraftBlockState implements BlockState { + + @Override + public Material getType() { +- return CraftMagicNumbers.getMaterial(data.getBlock()); ++ return 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 58f892478de74b853cd35ef2fec8c462e3a9ecee..1fe9b32c2a3717652be70786090a61c5fa7430cf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -45,7 +45,7 @@ public class CraftBlockData implements BlockData { + + @Override + public Material getMaterial() { +- return CraftMagicNumbers.getMaterial(state.getBlock()); ++ return state.getBukkitMaterial(); // Paper - optimise getType calls + } + + public IBlockData getState() { +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +index d02281f954aac8d8b65f5d36ec70f0352e4c7cdd..2194232200e90fa894b45eb265b5ed08ce1e35a3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +@@ -73,7 +73,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + + @Override + public Material getType(int x, int y, int z) { +- return CraftMagicNumbers.getMaterial(getTypeId(x, y, z).getBlock()); ++ return getTypeId(x, y, z).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override diff --git a/patches/server-unmapped/0001/0584-Villager-resetOffers.patch b/patches/server-unmapped/0001/0584-Villager-resetOffers.patch new file mode 100644 index 0000000000..b7dc3c01b1 --- /dev/null +++ b/patches/server-unmapped/0001/0584-Villager-resetOffers.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 7 Oct 2019 00:15:37 -0500 +Subject: [PATCH] Villager#resetOffers + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java +index 3e7c6416d1d812661a6f24920573cad1a0ca2503..adcb2070a871223abae0c055f1e88134c38a9e68 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java +@@ -113,6 +113,13 @@ public abstract class EntityVillagerAbstract extends EntityAgeable implements NP + return this.tradingPlayer != null; + } + ++ // Paper start ++ public void resetOffers() { ++ this.trades = new MerchantRecipeList(); ++ this.updateTrades(); ++ } ++ // Paper end ++ + @Override + public MerchantRecipeList getOffers() { + if (this.trades == null) { +@@ -234,6 +241,7 @@ public abstract class EntityVillagerAbstract extends EntityAgeable implements NP + return this.world; + } + ++ protected final void updateTrades() { eW(); } // Paper - OBFHELPER + protected abstract void eW(); + + protected void a(MerchantRecipeList merchantrecipelist, VillagerTrades.IMerchantRecipeOption[] avillagertrades_imerchantrecipeoption, int i) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java +index 01285d555ed084fa2e818e1f02955f8aa28002b3..ee751ed4f9f386bf9926347fe2f0958015aecffa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java +@@ -71,4 +71,11 @@ public class CraftAbstractVillager extends CraftAgeable implements AbstractVilla + public HumanEntity getTrader() { + return getMerchant().getTrader(); + } ++ ++ // Paper start ++ @Override ++ public void resetOffers() { ++ getHandle().resetOffers(); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0585-Improve-inlinig-for-some-hot-IBlockData-methods.patch b/patches/server-unmapped/0001/0585-Improve-inlinig-for-some-hot-IBlockData-methods.patch new file mode 100644 index 0000000000..124a856cdf --- /dev/null +++ b/patches/server-unmapped/0001/0585-Improve-inlinig-for-some-hot-IBlockData-methods.patch @@ -0,0 +1,101 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Jul 2020 20:46:50 -0700 +Subject: [PATCH] Improve inlinig for some hot IBlockData methods + + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +index 9af91784544dbb0555824a91088257659bdf2c3d..3fdafc0ff0c4148ec844dbdc1455d17cdcb4a75a 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +@@ -391,7 +391,14 @@ public abstract class BlockBase { + } + // Paper end + ++ // Paper start ++ protected boolean isTicking; ++ protected Fluid fluid; ++ // Paper end ++ + public void a() { ++ this.fluid = this.getBlock().d(this.p()); // Paper - moved from getFluid() ++ this.isTicking = this.getBlock().isTicking(this.p()); // Paper - moved from isTicking() + if (!this.getBlock().o()) { + this.a = new BlockBase.BlockData.Cache(this.p()); + } +@@ -430,19 +437,19 @@ public abstract class BlockBase { + return this.getBlock().d(this.p(), iblockaccess, blockposition); + } + +- public boolean d() { ++ public final boolean d() { // Paper + return this.a == null || this.a.c; + } + +- public boolean e() { ++ public final boolean e() { // Paper + return this.e; + } + +- public int f() { ++ public final int f() { // Paper + return this.b; + } + +- public boolean isAir() { ++ public final boolean isAir() { // Paper + return this.f; + } + +@@ -508,7 +515,7 @@ public abstract class BlockBase { + } + } + +- public boolean l() { ++ public final boolean l() { // Paper + return this.k; + } + +@@ -680,12 +687,12 @@ public abstract class BlockBase { + return this.getBlock().a(block); + } + +- public Fluid getFluid() { +- return this.getBlock().d(this.p()); ++ public final Fluid getFluid() { // Paper ++ return this.fluid; // Paper - moved into init + } + +- public boolean isTicking() { +- return this.getBlock().isTicking(this.p()); ++ public final boolean isTicking() { // Paper ++ return this.isTicking; // Paper - moved into init + } + + public SoundEffectType getStepSound() { +diff --git a/src/main/java/net/minecraft/world/level/material/Fluid.java b/src/main/java/net/minecraft/world/level/material/Fluid.java +index 9a93b204973eef4c02a52f33fd9578a43603ed98..147e628bd562da4cf6f07218724a9d6c79d26e38 100644 +--- a/src/main/java/net/minecraft/world/level/material/Fluid.java ++++ b/src/main/java/net/minecraft/world/level/material/Fluid.java +@@ -20,8 +20,12 @@ public final class Fluid extends IBlockDataHolder { + + public static final Codec a = a((Codec) IRegistry.FLUID, FluidType::h).stable(); + ++ // Paper start ++ protected final boolean isEmpty; ++ // Paper end + public Fluid(FluidType fluidtype, ImmutableMap, Comparable> immutablemap, MapCodec mapcodec) { + super(fluidtype, immutablemap, mapcodec); ++ this.isEmpty = fluidtype.b(); // Paper - moved from isEmpty() + } + + public FluidType getType() { +@@ -33,7 +37,7 @@ public final class Fluid extends IBlockDataHolder { + } + + public boolean isEmpty() { +- return this.getType().b(); ++ return this.isEmpty; // Paper - moved into constructor + } + + public float getHeight(IBlockAccess iblockaccess, BlockPosition blockposition) { diff --git a/patches/server-unmapped/0001/0586-Retain-block-place-order-when-capturing-blockstates.patch b/patches/server-unmapped/0001/0586-Retain-block-place-order-when-capturing-blockstates.patch new file mode 100644 index 0000000000..418df80756 --- /dev/null +++ b/patches/server-unmapped/0001/0586-Retain-block-place-order-when-capturing-blockstates.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 7 Aug 2020 04:27:56 -0700 +Subject: [PATCH] Retain block place order when capturing blockstates + +Fixes twisted vines not connecting properly when grown via +bonemeal by a player. + +In general, look at making this logic more robust (i.e properly handling +cases where a captured entry is overriden) - but for now this will do. + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 307a89c431739ed15d8869faace7b08927626f60..ae5f9a6af810b524f6dcbed64bc943122f0536cc 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -135,7 +135,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public boolean captureBlockStates = false; + public boolean captureTreeGeneration = false; + public Map capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper +- public Map capturedTileEntities = new HashMap<>(); ++ public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper + public List captureDrops; + public long ticksPerAnimalSpawns; + public long ticksPerMonsterSpawns; diff --git a/patches/server-unmapped/0001/0587-Reduce-blockpos-allocation-from-pathfinding.patch b/patches/server-unmapped/0001/0587-Reduce-blockpos-allocation-from-pathfinding.patch new file mode 100644 index 0000000000..2640189324 --- /dev/null +++ b/patches/server-unmapped/0001/0587-Reduce-blockpos-allocation-from-pathfinding.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 25 Apr 2020 17:10:55 -0700 +Subject: [PATCH] Reduce blockpos allocation from pathfinding + + +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java +index d14f2800237c2a80912bf6f2d418a9ba9031070d..a0c7d3ab747ba1a3cf07e716f3591663a8a9e14b 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathfinderNormal.java +@@ -498,7 +498,7 @@ public class PathfinderNormal extends PathfinderAbstract { + return PathType.DANGER_FIRE; + } + +- if (iblockaccess.getFluid(blockposition_mutableblockposition).a((Tag) TagsFluid.WATER)) { ++ if (iblockdata.getFluid().a((Tag) TagsFluid.WATER)) { // Paper - remove another getType call + return PathType.WATER_BORDER; + } + } // Paper +@@ -528,7 +528,7 @@ public class PathfinderNormal extends PathfinderAbstract { + } else if (iblockdata.a(Blocks.COCOA)) { + return PathType.COCOA; + } else { +- Fluid fluid = iblockaccess.getFluid(blockposition); ++ Fluid fluid = iblockdata.getFluid(); // Paper - remove another get type call + + return fluid.a((Tag) TagsFluid.WATER) ? PathType.WATER : (fluid.a((Tag) TagsFluid.LAVA) ? PathType.LAVA : (a(iblockdata) ? PathType.DAMAGE_FIRE : (BlockDoor.l(iblockdata) && !(Boolean) iblockdata.get(BlockDoor.OPEN) ? PathType.DOOR_WOOD_CLOSED : (block instanceof BlockDoor && material == Material.ORE && !(Boolean) iblockdata.get(BlockDoor.OPEN) ? PathType.DOOR_IRON_CLOSED : (block instanceof BlockDoor && (Boolean) iblockdata.get(BlockDoor.OPEN) ? PathType.DOOR_OPEN : (block instanceof BlockMinecartTrackAbstract ? PathType.RAIL : (block instanceof BlockLeaves ? PathType.LEAVES : (!block.a((Tag) TagsBlock.FENCES) && !block.a((Tag) TagsBlock.WALLS) && (!(block instanceof BlockFenceGate) || (Boolean) iblockdata.get(BlockFenceGate.OPEN)) ? (!iblockdata.a(iblockaccess, blockposition, PathMode.LAND) ? PathType.BLOCKED : PathType.OPEN) : PathType.FENCE)))))))); + } diff --git a/patches/server-unmapped/0001/0588-Fix-item-locations-dropped-from-campfires.patch b/patches/server-unmapped/0001/0588-Fix-item-locations-dropped-from-campfires.patch new file mode 100644 index 0000000000..764f6d7979 --- /dev/null +++ b/patches/server-unmapped/0001/0588-Fix-item-locations-dropped-from-campfires.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 3 Oct 2020 20:32:25 -0500 +Subject: [PATCH] Fix item locations dropped from campfires + +Fixes #4259 by not flooring the blockposition among other weirdness + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java +index df010dc740f2dd647a418ba4e425ce3a3b1d79e2..62a19f39405cff27f34a3b98fb9310b1c9c27563 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java +@@ -14,6 +14,7 @@ import net.minecraft.world.Clearable; + import net.minecraft.world.ContainerUtil; + import net.minecraft.world.InventorySubcontainer; + import net.minecraft.world.InventoryUtils; ++import net.minecraft.world.entity.item.EntityItem; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.crafting.RecipeCampfire; + import net.minecraft.world.item.crafting.Recipes; +@@ -92,7 +93,11 @@ public class TileEntityCampfire extends TileEntity implements Clearable, ITickab + result = blockCookEvent.getResult(); + itemstack1 = CraftItemStack.asNMSCopy(result); + // CraftBukkit end +- InventoryUtils.dropItem(this.world, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), itemstack1); ++ // Paper start ++ EntityItem droppedItem = new EntityItem(this.world, blockposition.getX() + 0.5D, blockposition.getY() + 0.5D, blockposition.getZ() + 0.5D, itemstack1.cloneAndSubtract(this.world.random.nextInt(21) + 10)); ++ droppedItem.setMot(this.world.random.nextGaussian() * 0.05D, this.world.random.nextGaussian() * 0.05D + 0.2D, this.world.random.nextGaussian() * 0.05D); ++ this.world.addEntity(droppedItem); ++ // Paper end + this.items.set(i, ItemStack.b); + this.k(); + } diff --git a/patches/server-unmapped/0001/0589-Player-elytra-boost-API.patch b/patches/server-unmapped/0001/0589-Player-elytra-boost-API.patch new file mode 100644 index 0000000000..4b422c7169 --- /dev/null +++ b/patches/server-unmapped/0001/0589-Player-elytra-boost-API.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Tue, 14 Apr 2020 12:05:22 +0200 +Subject: [PATCH] Player elytra boost API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index a4726747c066d623f37162e2637403efee7f5708..204d3e879c736f44e01f9246af26f6cccf8d72a7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -69,12 +69,14 @@ import net.minecraft.world.entity.ai.attributes.AttributeMapBase; + import net.minecraft.world.entity.ai.attributes.AttributeModifiable; + import net.minecraft.world.entity.ai.attributes.GenericAttributes; + import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.entity.projectile.EntityFireworks; + import net.minecraft.world.inventory.Container; + import net.minecraft.world.item.EnumColor; + import net.minecraft.world.item.enchantment.EnchantmentManager; + import net.minecraft.world.item.enchantment.Enchantments; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.EnumGamemode; ++import net.minecraft.world.level.World; + import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.block.entity.TileEntitySign; + import net.minecraft.world.level.saveddata.maps.MapIcon; +@@ -2281,6 +2283,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + throw new RuntimeException("Unknown settings type"); + } ++ ++ @Override ++ public org.bukkit.entity.Firework boostElytra(ItemStack firework) { ++ Validate.isTrue(isGliding(), "Player must be gliding"); ++ Validate.isTrue(firework != null, "firework == null"); ++ Validate.isTrue(firework.getType() == Material.FIREWORK_ROCKET, "Firework must be Material.FIREWORK_ROCKET"); ++ ++ net.minecraft.world.item.ItemStack item = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(firework); ++ World world = ((CraftWorld) getWorld()).getHandle(); ++ EntityFireworks entity = new EntityFireworks(world, item, getHandle()); ++ return world.addEntity(entity) ++ ? (org.bukkit.entity.Firework) entity.getBukkitEntity() ++ : null; ++ } + // Paper end + + // Spigot start diff --git a/patches/server-unmapped/0001/0590-Fixed-TileEntityBell-memory-leak.patch b/patches/server-unmapped/0001/0590-Fixed-TileEntityBell-memory-leak.patch new file mode 100644 index 0000000000..879f752716 --- /dev/null +++ b/patches/server-unmapped/0001/0590-Fixed-TileEntityBell-memory-leak.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: giacomo <32515303+giacomozama@users.noreply.github.com> +Date: Sat, 10 Oct 2020 12:15:33 +0200 +Subject: [PATCH] Fixed TileEntityBell memory leak + +TileEntityBell has a list of entities (entitiesAtRing) that was not being cleared at the right time, causing leaks whenever a bell would be rung near a crowd of entities. + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java +index e97d229da3cf7631555f418a73bc74255494cc01..84f9f52c5b632621b509448ac1c760f64de6b062 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBell.java +@@ -27,8 +27,8 @@ public class TileEntityBell extends TileEntity implements ITickable { + public int a; + public boolean b; + public EnumDirection c; +- private List h; +- private boolean i; ++ private List h; private List getEntitiesAtRing() { return this.h; } // Paper - OBFHELPER ++ private boolean i; private boolean getShouldReveal() { return this.i; } // Paper - OBFHELPER + private int j; + + public TileEntityBell() { +@@ -57,6 +57,11 @@ public class TileEntityBell extends TileEntity implements ITickable { + + if (this.a >= 50) { + this.b = false; ++ // Paper start ++ if (!this.getShouldReveal()) { ++ this.getEntitiesAtRing().clear(); ++ } ++ // Paper end + this.a = 0; + } + +@@ -71,6 +76,7 @@ public class TileEntityBell extends TileEntity implements ITickable { + } else { + this.a(this.world); + this.b(this.world); ++ this.getEntitiesAtRing().clear(); // Paper + this.i = false; + } + } +@@ -111,11 +117,12 @@ public class TileEntityBell extends TileEntity implements ITickable { + EntityLiving entityliving = (EntityLiving) iterator.next(); + + if (entityliving.isAlive() && !entityliving.dead && blockposition.a((IPosition) entityliving.getPositionVector(), 32.0D)) { +- entityliving.getBehaviorController().setMemory(MemoryModuleType.HEARD_BELL_TIME, (Object) this.world.getTime()); ++ entityliving.getBehaviorController().setMemory(MemoryModuleType.HEARD_BELL_TIME, this.world.getTime()); // Paper - decompile fix + } + } + } + ++ this.getEntitiesAtRing().removeIf(e -> !e.isAlive()); // Paper + } + + private boolean h() { diff --git a/patches/server-unmapped/0001/0591-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch b/patches/server-unmapped/0001/0591-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch new file mode 100644 index 0000000000..775fe3cc1e --- /dev/null +++ b/patches/server-unmapped/0001/0591-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Toon Schoenmakers +Date: Fri, 23 Oct 2020 15:01:44 +0200 +Subject: [PATCH] Avoid error bubbling up when item stack is empty in fishing + loot + +This can realistically only happen if there's custom loot active on fishing +which can return 0 items. This would disconnect the player who's fishing. + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java +index d40b056b2ff14033113bd7108a3295f8783b8bdf..addea9c1309a308b76c93ee71e839c915bc773e8 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityFishingHook.java +@@ -484,9 +484,15 @@ public class EntityFishingHook extends IProjectile { + + while (iterator.hasNext()) { + ItemStack itemstack1 = (ItemStack) iterator.next(); +- EntityItem entityitem = new EntityItem(this.world, this.locX(), this.locY(), this.locZ(), itemstack1); ++ // Paper start, new EntityItem would throw if for whatever reason (mostly shitty datapacks) the itemstack1 turns out to be empty ++ // if the item stack is empty we instead just have our entityitem as null ++ EntityItem entityitem = null; ++ if (!itemstack1.isEmpty()) { ++ entityitem = new EntityItem(this.world, this.locX(), this.locY(), this.locZ(), itemstack1); ++ } ++ // Paper end + // CraftBukkit start +- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem.getBukkitEntity(), (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); ++ 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.setExpToDrop(this.random.nextInt(6) + 1); + this.world.getServer().getPluginManager().callEvent(playerFishEvent); + +@@ -499,8 +505,12 @@ public class EntityFishingHook extends IProjectile { + double d2 = entityhuman.locZ() - this.locZ(); + double d3 = 0.1D; + +- entityitem.setMot(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); +- this.world.addEntity(entityitem); ++ // Paper start, entity item can be null, so we need to check against this ++ if (entityitem != null) { ++ entityitem.setMot(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); ++ this.world.addEntity(entityitem); ++ } ++ // Paper end + // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() + if (playerFishEvent.getExpToDrop() > 0) { + entityhuman.world.addEntity(new EntityExperienceOrb(entityhuman.world, entityhuman.locX(), entityhuman.locY() + 0.5D, entityhuman.locZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getOwner(), this)); // Paper diff --git a/patches/server-unmapped/0001/0592-Add-getOfflinePlayerIfCached-String.patch b/patches/server-unmapped/0001/0592-Add-getOfflinePlayerIfCached-String.patch new file mode 100644 index 0000000000..108e44fb11 --- /dev/null +++ b/patches/server-unmapped/0001/0592-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 1199644c6342379df21840aa144e731e9e9245ca..fe2ad3f9298af4d405711b4798e34ae484a19db9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1607,6 +1607,28 @@ public final class CraftServer implements Server { + return result; + } + ++ // Paper start ++ @Override ++ @Nullable ++ public OfflinePlayer getOfflinePlayerIfCached(String name) { ++ Validate.notNull(name, "Name cannot be null"); ++ Validate.notEmpty(name, "Name cannot be empty"); ++ ++ OfflinePlayer result = getPlayerExact(name); ++ if (result == null) { ++ GameProfile profile = console.getUserCache().getProfileIfCached(name); ++ ++ if (profile != null) { ++ result = getOfflinePlayer(profile); ++ } ++ } else { ++ offlinePlayers.remove(result.getUniqueId()); ++ } ++ ++ return result; ++ } ++ // Paper end ++ + @Override + public OfflinePlayer getOfflinePlayer(UUID id) { + Validate.notNull(id, "UUID cannot be null"); diff --git a/patches/server-unmapped/0001/0593-Add-ignore-discounts-API.patch b/patches/server-unmapped/0001/0593-Add-ignore-discounts-API.patch new file mode 100644 index 0000000000..9904971520 --- /dev/null +++ b/patches/server-unmapped/0001/0593-Add-ignore-discounts-API.patch @@ -0,0 +1,143 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Mon, 9 Nov 2020 20:44:51 +0100 +Subject: [PATCH] Add ignore discounts API + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +index 80a9eecfd2eb17db6a7d0b3973f4acdb80f873e8..382059b2c2b6a7b8231b3c3a5c9a401091b1ad18 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +@@ -460,6 +460,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + + while (iterator.hasNext()) { + MerchantRecipe merchantrecipe = (MerchantRecipe) iterator.next(); ++ if (merchantrecipe.ignoreDiscounts) continue; // Paper + + // CraftBukkit start + int bonus = -MathHelper.d((float) i * merchantrecipe.getPriceMultiplier()); +@@ -479,6 +480,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + + while (iterator1.hasNext()) { + MerchantRecipe merchantrecipe1 = (MerchantRecipe) iterator1.next(); ++ if (merchantrecipe1.ignoreDiscounts) continue; // Paper + double d0 = 0.3D + 0.0625D * (double) j; + int k = (int) Math.floor(d0 * (double) merchantrecipe1.a().getCount()); + +diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantRecipe.java b/src/main/java/net/minecraft/world/item/trading/MerchantRecipe.java +index 9e2fe0d5e6d4ea1f4c9cea96b755ddcd1e3c9009..605d1b2514c272c0fcf3cb9d7575ad8066dad31b 100644 +--- a/src/main/java/net/minecraft/world/item/trading/MerchantRecipe.java ++++ b/src/main/java/net/minecraft/world/item/trading/MerchantRecipe.java +@@ -19,6 +19,7 @@ public class MerchantRecipe { + private int demand; + public float priceMultiplier; + public int xp; ++ public boolean ignoreDiscounts; // Paper + // CraftBukkit start + private CraftMerchantRecipe bukkitHandle; + +@@ -27,7 +28,12 @@ public class MerchantRecipe { + } + + public MerchantRecipe(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, CraftMerchantRecipe bukkit) { +- this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier); ++ // Paper start - add ignoreDiscounts param ++ this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, false, bukkit); ++ } ++ public MerchantRecipe(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, boolean ignoreDiscounts, CraftMerchantRecipe bukkit) { ++ this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, ignoreDiscounts); ++ // Paper end + this.bukkitHandle = bukkit; + } + // CraftBukkit end +@@ -59,6 +65,7 @@ public class MerchantRecipe { + + this.specialPrice = nbttagcompound.getInt("specialPrice"); + this.demand = nbttagcompound.getInt("demand"); ++ this.ignoreDiscounts = nbttagcompound.getBoolean("Paper.IgnoreDiscounts"); // Paper + } + + public MerchantRecipe(ItemStack itemstack, ItemStack itemstack1, int i, int j, float f) { +@@ -70,10 +77,19 @@ public class MerchantRecipe { + } + + public MerchantRecipe(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int i, int j, int k, float f) { +- this(itemstack, itemstack1, itemstack2, i, j, k, f, 0); ++ // Paper start - add ignoreDiscounts param ++ this(itemstack, itemstack1, itemstack2, i, j, k, f, false); ++ } ++ public MerchantRecipe(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int i, int j, int k, float f, boolean ignoreDiscounts) { ++ this(itemstack, itemstack1, itemstack2, i, j, k, f, 0, ignoreDiscounts); + } + + public MerchantRecipe(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int i, int j, int k, float f, int l) { ++ this(itemstack, itemstack1, itemstack2, i, j, k, f, l, false); ++ } ++ public MerchantRecipe(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int i, int j, int k, float f, int l, boolean ignoreDiscounts) { ++ this.ignoreDiscounts = ignoreDiscounts; ++ // Paper end + this.rewardExp = true; + this.xp = 1; + this.buyingItem1 = itemstack; +@@ -189,6 +205,7 @@ public class MerchantRecipe { + nbttagcompound.setFloat("priceMultiplier", this.priceMultiplier); + nbttagcompound.setInt("specialPrice", this.specialPrice); + nbttagcompound.setInt("demand", this.demand); ++ nbttagcompound.setBoolean("Paper.IgnoreDiscounts", this.ignoreDiscounts); // Paper + return nbttagcompound; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java +index e3b9a0ebe5cec5b47d9d225887656e00e999960b..0c8728bd8272e36d70cf8f05fcd7656ea5a48702 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java +@@ -17,7 +17,12 @@ public class CraftMerchantRecipe extends MerchantRecipe { + } + + public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier) { +- super(result, uses, maxUses, experienceReward, experience, priceMultiplier); ++ // Paper start - add ignoreDiscounts param ++ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, false); ++ } ++ public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, boolean ignoreDiscounts) { ++ super(result, uses, maxUses, experienceReward, experience, priceMultiplier, ignoreDiscounts); ++ // Paper end + this.handle = new net.minecraft.world.item.trading.MerchantRecipe( + net.minecraft.world.item.ItemStack.b, + net.minecraft.world.item.ItemStack.b, +@@ -26,6 +31,7 @@ public class CraftMerchantRecipe extends MerchantRecipe { + maxUses, + experience, + priceMultiplier, ++ ignoreDiscounts, // Paper - add ignoreDiscounts param + this + ); + this.setExperienceReward(experienceReward); +@@ -81,6 +87,18 @@ public class CraftMerchantRecipe extends MerchantRecipe { + handle.priceMultiplier = priceMultiplier; + } + ++ // Paper start ++ @Override ++ public boolean shouldIgnoreDiscounts() { ++ return this.handle.ignoreDiscounts; ++ } ++ ++ @Override ++ public void setIgnoreDiscounts(boolean ignoreDiscounts) { ++ this.handle.ignoreDiscounts = ignoreDiscounts; ++ } ++ // Paper end ++ + public net.minecraft.world.item.trading.MerchantRecipe toMinecraft() { + List ingredients = getIngredients(); + Preconditions.checkState(!ingredients.isEmpty(), "No offered ingredients"); +@@ -95,7 +113,7 @@ public class CraftMerchantRecipe extends MerchantRecipe { + if (recipe instanceof CraftMerchantRecipe) { + return (CraftMerchantRecipe) recipe; + } else { +- CraftMerchantRecipe craft = new CraftMerchantRecipe(recipe.getResult(), recipe.getUses(), recipe.getMaxUses(), recipe.hasExperienceReward(), recipe.getVillagerExperience(), recipe.getPriceMultiplier()); ++ CraftMerchantRecipe craft = new CraftMerchantRecipe(recipe.getResult(), recipe.getUses(), recipe.getMaxUses(), recipe.hasExperienceReward(), recipe.getVillagerExperience(), recipe.getPriceMultiplier(), recipe.shouldIgnoreDiscounts()); // Paper - shouldIgnoreDiscounts + craft.setIngredients(recipe.getIngredients()); + + return craft; diff --git a/patches/server-unmapped/0001/0594-Toggle-for-removing-existing-dragon.patch b/patches/server-unmapped/0001/0594-Toggle-for-removing-existing-dragon.patch new file mode 100644 index 0000000000..ab99c1b234 --- /dev/null +++ b/patches/server-unmapped/0001/0594-Toggle-for-removing-existing-dragon.patch @@ -0,0 +1,36 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 02bb85364560784adea47c877c13291c3d016b86..424754a0183b071d20c86f0420cec784a8992e2b 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -683,4 +683,12 @@ public class PaperWorldConfig { + log("Using vanilla redstone algorithm."); + } + } ++ ++ public boolean shouldRemoveDragon = false; ++ private void shouldRemoveDragon() { ++ shouldRemoveDragon = getBoolean("should-remove-dragon", shouldRemoveDragon); ++ if (shouldRemoveDragon) { ++ log("The Ender Dragon will be removed if she already exists without a portal."); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +index adfd36d9e62828c148726f0f26dfb1825e32abf1..a255675375d0e50a45694f09056a98dbd7449ecd 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +@@ -214,7 +214,7 @@ public class EnderDragonBattle { + this.dragonUUID = entityenderdragon.getUniqueID(); + EnderDragonBattle.LOGGER.info("Found that there's a dragon still alive ({})", entityenderdragon); + this.dragonKilled = false; +- if (!flag) { ++ if (!flag && this.world.paperConfig.shouldRemoveDragon) { // Paper + EnderDragonBattle.LOGGER.info("But we didn't have a portal, let's remove it."); + entityenderdragon.die(); + this.dragonUUID = null; diff --git a/patches/server-unmapped/0001/0595-Fix-client-lag-on-advancement-loading.patch b/patches/server-unmapped/0001/0595-Fix-client-lag-on-advancement-loading.patch new file mode 100644 index 0000000000..28cd03fb92 --- /dev/null +++ b/patches/server-unmapped/0001/0595-Fix-client-lag-on-advancement-loading.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +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/net/minecraft/server/AdvancementDataPlayer.java b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java +index 8e760445885f6ab92f60db0ee2a02d098b5e5f03..7a8a1960882e291c46301d07da3e1c5415516893 100644 +--- a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java ++++ b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java +@@ -97,6 +97,7 @@ public class AdvancementDataPlayer { + + } + ++ public final void reload(AdvancementDataWorld advancementDataWorld) { this.a(advancementDataWorld); } // Paper - OBFHELPER + public void a(AdvancementDataWorld advancementdataworld) { + this.a(); + this.data.clear(); +@@ -393,6 +394,7 @@ public class AdvancementDataPlayer { + + } + ++ public final void sendUpdateIfNeeded(EntityPlayer entityPlayer) { this.b(entityPlayer); } // Paper - OBFHELPER + public void b(EntityPlayer entityplayer) { + if (this.m || !this.i.isEmpty() || !this.j.isEmpty()) { + Map map = Maps.newHashMap(); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index a1c918e84627d79f6665237851f614880a9da6f7..74ebd6257ca7c87bcedff831d213273e7d542612 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -309,7 +309,13 @@ public final class CraftMagicNumbers implements UnsafeValues { + Bukkit.getLogger().log(Level.SEVERE, "Error saving advancement " + key, ex); + } + +- MinecraftServer.getServer().getPlayerList().reload(); ++ // Paper start ++ //MinecraftServer.getServer().getPlayerList().reload(); ++ MinecraftServer.getServer().getPlayerList().getPlayers().forEach(player -> { ++ player.getAdvancementData().reload(MinecraftServer.getServer().getAdvancementData()); ++ player.getAdvancementData().sendUpdateIfNeeded(player); ++ }); ++ // Paper end + + return bukkit; + } diff --git a/patches/server-unmapped/0001/0596-Item-no-age-no-player-pickup.patch b/patches/server-unmapped/0001/0596-Item-no-age-no-player-pickup.patch new file mode 100644 index 0000000000..84124da98a --- /dev/null +++ b/patches/server-unmapped/0001/0596-Item-no-age-no-player-pickup.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alfie Smith +Date: Sat, 7 Nov 2020 01:20:33 +0000 +Subject: [PATCH] Item no age & no player pickup + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +index 5988cdd18b7e4bfca0075fd2356cfe9c4e673954..7a78ef2f6f673568c0528fa46168c69d21f51a66 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +@@ -10,6 +10,12 @@ import org.bukkit.entity.Item; + import org.bukkit.inventory.ItemStack; + + public class CraftItem extends CraftEntity implements Item { ++ ++ // Paper start ++ private final static int NO_AGE_TIME = (int) Short.MIN_VALUE; ++ private final static int NO_PICKUP_TIME = (int) Short.MAX_VALUE; ++ // Paper end ++ + private final EntityItem item; + + public CraftItem(CraftServer server, Entity entity, EntityItem item) { +@@ -57,6 +63,26 @@ public class CraftItem extends CraftEntity implements Item { + public void setCanMobPickup(boolean canMobPickup) { + item.canMobPickup = canMobPickup; + } ++ ++ @Override ++ public boolean canPlayerPickup() { ++ return item.pickupDelay != NO_PICKUP_TIME; ++ } ++ ++ @Override ++ public void setCanPlayerPickup(boolean canPlayerPickup) { ++ item.pickupDelay = canPlayerPickup ? 0 : NO_PICKUP_TIME; ++ } ++ ++ @Override ++ public boolean willAge() { ++ return item.age != NO_AGE_TIME; ++ } ++ ++ @Override ++ public void setWillAge(boolean willAge) { ++ item.age = willAge ? 0 : NO_AGE_TIME; ++ } + // Paper End + + @Override diff --git a/patches/server-unmapped/0001/0597-Beacon-API-custom-effect-ranges.patch b/patches/server-unmapped/0001/0597-Beacon-API-custom-effect-ranges.patch new file mode 100644 index 0000000000..f32bda9f01 --- /dev/null +++ b/patches/server-unmapped/0001/0597-Beacon-API-custom-effect-ranges.patch @@ -0,0 +1,90 @@ +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/TileEntityBeacon.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeacon.java +index f9b1ab0e19ff398a16b1452e86f1a165a4b54219..8dfb9eb12d07c2d4737ecf3de1ae7f6de31891bf 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeacon.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBeacon.java +@@ -73,6 +73,26 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic + return (hasSecondaryEffect()) ? CraftPotionUtil.toBukkit(new MobEffect(this.secondaryEffect, getLevel(), getAmplification(), true, true)) : null; + } + // CraftBukkit end ++ // Paper start - add field/methods for custom range ++ 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 + + public TileEntityBeacon() { + super(TileEntityTypes.BEACON); +@@ -263,7 +283,8 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic + + public List getHumansInRange() { + { +- double d0 = (double) (this.levels * 10 + 10); ++ // Paper - custom beacon ranges ++ double d0 = this.getEffectRange(); + + AxisAlignedBB axisalignedbb = (new AxisAlignedBB(this.position)).g(d0).b(0.0D, (double) this.world.getBuildHeight(), 0.0D); + List list = this.world.a(EntityHuman.class, axisalignedbb); +@@ -364,6 +385,9 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic + this.secondaryEffect = MobEffectList.fromId(nbttagcompound.getInt("Secondary")); + this.levels = nbttagcompound.getInt("Levels"); // SPIGOT-5053, use where available + // CraftBukkit end ++ // Paper ++ this.effectRange = nbttagcompound.hasKeyOfType(PAPER_RANGE_TAG, 6) ? nbttagcompound.getDouble(PAPER_RANGE_TAG) : -1; ++ + if (nbttagcompound.hasKeyOfType("CustomName", 8)) { + this.customName = IChatBaseComponent.ChatSerializer.a(nbttagcompound.getString("CustomName")); + } +@@ -380,6 +404,8 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic + if (this.customName != null) { + nbttagcompound.setString("CustomName", IChatBaseComponent.ChatSerializer.a(this.customName)); + } ++ // Paper ++ nbttagcompound.setDouble(PAPER_RANGE_TAG, this.effectRange); + + this.chestLock.a(nbttagcompound); + return nbttagcompound; +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +index d3ae5cadd88f9012203d2c04cbe38af9b215ef0b..c2e054853cba891326d45e9129bbc09e32fa3cc7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +@@ -108,4 +108,19 @@ public class CraftBeacon extends CraftBlockEntityState impleme + public void setLock(String key) { + this.getSnapshot().chestLock = (key == null) ? ChestLock.a : new ChestLock(key); + } ++ ++ @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(); ++ } + } diff --git a/patches/server-unmapped/0001/0598-Add-API-for-quit-reason.patch b/patches/server-unmapped/0001/0598-Add-API-for-quit-reason.patch new file mode 100644 index 0000000000..afea5814c9 --- /dev/null +++ b/patches/server-unmapped/0001/0598-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/NetworkManager.java b/src/main/java/net/minecraft/network/NetworkManager.java +index 878f879f8d410c428ad8a4c49e0c86c559bc47a9..f86f430598026a3a7e27fb8d40cfc5fe7b9b845d 100644 +--- a/src/main/java/net/minecraft/network/NetworkManager.java ++++ b/src/main/java/net/minecraft/network/NetworkManager.java +@@ -137,12 +137,15 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + + this.u = true; + if (this.channel.isOpen()) { ++ EntityPlayer player = this.getPlayer(); // Paper + if (throwable instanceof TimeoutException) { + NetworkManager.LOGGER.debug("Timeout", throwable); ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper + this.close(new ChatMessage("disconnect.timeout")); + } else { + ChatMessage chatmessage = new ChatMessage("disconnect.genericReason", new Object[]{"Internal Exception: " + throwable}); + ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper + if (flag) { + NetworkManager.LOGGER.debug("Failed to sent packet", throwable); + this.sendPacket(new PacketPlayOutKickDisconnect(chatmessage), (future) -> { +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 1a08be6a490f0b94f86ee87ad06e60b66afc63f9..d850721afc33230890353f16c5bc5579c9efb1bf 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -260,6 +260,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks + + boolean needsChunkCenterUpdate; // Paper - no-tick view distance ++ public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event + + public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) { + super(worldserver, worldserver.getSpawn(), worldserver.v(), gameprofile); +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 8ffc98faecca616005fc56031718a7a10de40102..d00c17210b3c0aff40b37ff11f8e9dc6ae1ba948 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -449,6 +449,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + final IChatBaseComponent ichatbasecomponent = PaperAdventure.asVanilla(event.reason()); // Paper - Adventure + // CraftBukkit end + ++ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper + this.networkManager.sendPacket(new PacketPlayOutKickDisconnect(ichatbasecomponent), (future) -> { + this.networkManager.close(ichatbasecomponent); + }); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 272a899c4d599a97201f1c11d829abe40101377d..61513e3c27cf95090e61f7085bb2f0435c774df9 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -590,7 +590,7 @@ public abstract class PlayerList { + entityplayer.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper + } + +- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getName()))); ++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getName())), entityplayer.quitReason); // Paper - quit reason + if (entityplayer.didPlayerJoinEvent) cserver.getPluginManager().callEvent(playerQuitEvent); // Paper - if we disconnected before join ever fired, don't fire quit + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + diff --git a/patches/server-unmapped/0001/0599-Seed-based-feature-search.patch b/patches/server-unmapped/0001/0599-Seed-based-feature-search.patch new file mode 100644 index 0000000000..00261682eb --- /dev/null +++ b/patches/server-unmapped/0001/0599-Seed-based-feature-search.patch @@ -0,0 +1,97 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Mon, 13 Jan 2020 15:40:32 +0100 +Subject: [PATCH] Seed based feature search + +This tries to work around the issue where the server will load +surrounding chunks up to a radius of 100 chunks in order to search for +features e.g. when running the /locate command or for treasure maps +(issue #2312). +This is done by backporting Mojang's change in 1.17 which makes it so +that the biome (generated by the seed) is checked first if the feature +can be generated before actually to load the chunk. + +The only downside of this is that it breaks once the seed or generator +changes but this should usually not happen. A config option to disable +this improvement is added though in case that should ever be necessary. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 424754a0183b071d20c86f0420cec784a8992e2b..a98e25917193043633e2120beb4fe2d49d0e4500 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -337,6 +337,12 @@ public class PaperWorldConfig { + } + } + ++ public boolean seedBasedFeatureSearch = true; ++ private void seedBasedFeatureSearch() { ++ seedBasedFeatureSearch = getBoolean("seed-based-feature-search", seedBasedFeatureSearch); ++ log("Feature search is based on seed: " + seedBasedFeatureSearch); ++ } ++ + public int maxCollisionsPerEntity; + private void maxEntityCollision() { + maxCollisionsPerEntity = getInt( "max-entity-collisions", this.spigotConfig.getInt("max-entity-collisions", 8) ); +diff --git a/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java b/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java +index e41d63596c32eee5f0c04a6f043d576d8021ff1a..53eb5b65683a2ab208edfc3f3bbf78ffee61bc85 100644 +--- a/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java ++++ b/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java +@@ -68,10 +68,12 @@ public class ChunkCoordIntPair { + } + } + ++ public int getBlockX() { return d(); } // Paper - OBFHELPER + public int d() { + return this.x << 4; + } + ++ public int getBlockZ() { return e(); } // Paper - OBFHELPER + public int e() { + return this.z << 4; + } +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index ae5f9a6af810b524f6dcbed64bc943122f0536cc..f40be366ebc5f98b417b677565fa89d3f817f3fb 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -1511,8 +1511,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return this.methodProfiler; + } + +- @Override +- public BiomeManager d() { ++ public BiomeManager getBiomeManager() { return d(); } // Paper - OBFHELPER ++ @Override public BiomeManager d() { + return this.biomeManager; + } + +diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java +index 340508e0ba8b8883a3037ecaa2d4e09e61e709d3..3b1d9b26ba3249b1df0c15a22428e4211ae0e024 100644 +--- a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java ++++ b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java +@@ -23,6 +23,7 @@ public class BiomeManager { + return new BiomeManager(worldchunkmanager, this.b, this.c); + } + ++ public BiomeBase getBiome(BlockPosition blockposition) { return a(blockposition); } // Paper - OBFHELPER + public BiomeBase a(BlockPosition blockposition) { + return this.c.a(this.b, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this.a); + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java +index ea7e3e15fa778c573d24f956f72f60579ea0b1a1..6df42ff26ff6e65ec2885122fe53dde857a3d1d2 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureGenerator.java +@@ -176,6 +176,14 @@ public abstract class StructureGenerator + int j2 = i1 + k * l1; + ChunkCoordIntPair chunkcoordintpair = this.a(structuresettingsfeature, j, seededrandom, i2, j2); + if (!iworldreader.getWorldBorder().isChunkInBounds(chunkcoordintpair.x, chunkcoordintpair.z)) { continue; } // Paper ++ // Paper start - seed based feature search ++ if (structuremanager.getWorld().paperConfig.seedBasedFeatureSearch) { ++ BiomeBase biomeBase = structuremanager.getWorld().getBiomeManager().getBiome(new BlockPosition(chunkcoordintpair.getBlockX() + 9, 0, chunkcoordintpair.getBlockZ() + 9)); ++ if (!biomeBase.e().a(this)) { ++ continue; ++ } ++ } ++ // Paper end + IChunkAccess ichunkaccess = iworldreader.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.STRUCTURE_STARTS); + StructureStart structurestart = structuremanager.a(SectionPosition.a(ichunkaccess.getPos(), 0), this, ichunkaccess); + diff --git a/patches/server-unmapped/0001/0600-Add-Wandering-Trader-spawn-rate-config-options.patch b/patches/server-unmapped/0001/0600-Add-Wandering-Trader-spawn-rate-config-options.patch new file mode 100644 index 0000000000..7ec0e046ea --- /dev/null +++ b/patches/server-unmapped/0001/0600-Add-Wandering-Trader-spawn-rate-config-options.patch @@ -0,0 +1,120 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Thu, 20 Aug 2020 11:20:12 -0700 +Subject: [PATCH] Add Wandering Trader spawn rate config options + +Adds config options for modifying the spawn rates of Wandering Traders. +These values are all easy to understand and configure after a quick read of this +page on the Minecraft wiki: https://minecraft.gamepedia.com/Wandering_Trader#Spawning +Usages of the vanilla WanderingTraderSpawnDelay and WanderingTraderSpawnChance values +in IWorldServerData are removed as they were only used in certain places, with hardcoded +values used in other places. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index a98e25917193043633e2120beb4fe2d49d0e4500..b4d76494851601d61a69e2f060727a68f4461267 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -697,4 +697,17 @@ public class PaperWorldConfig { + log("The Ender Dragon will be removed if she already exists without a portal."); + } + } ++ ++ public int wanderingTraderSpawnMinuteTicks = 1200; ++ public int wanderingTraderSpawnDayTicks = 24000; ++ public int wanderingTraderSpawnChanceFailureIncrement = 25; ++ public int wanderingTraderSpawnChanceMin = 25; ++ public int wanderingTraderSpawnChanceMax = 75; ++ private void wanderingTraderSettings() { ++ wanderingTraderSpawnMinuteTicks = getInt("wandering-trader.spawn-minute-length", wanderingTraderSpawnMinuteTicks); ++ wanderingTraderSpawnDayTicks = getInt("wandering-trader.spawn-day-length", wanderingTraderSpawnDayTicks); ++ wanderingTraderSpawnChanceFailureIncrement = getInt("wandering-trader.spawn-chance-failure-increment", wanderingTraderSpawnChanceFailureIncrement); ++ wanderingTraderSpawnChanceMin = getInt("wandering-trader.spawn-chance-min", wanderingTraderSpawnChanceMin); ++ wanderingTraderSpawnChanceMax = getInt("wandering-trader.spawn-chance-max", wanderingTraderSpawnChanceMax); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/MobSpawnerTrader.java b/src/main/java/net/minecraft/world/entity/npc/MobSpawnerTrader.java +index b74a262a62642f63fdbd17579d58d5eae68ed169..e57938b4591bb103b9dd0d0145a62b5a901f2c63 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/MobSpawnerTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/MobSpawnerTrader.java +@@ -30,49 +30,59 @@ public class MobSpawnerTrader implements MobSpawner { + + private final Random a = new Random(); + private final IWorldDataServer b; +- private int c; +- private int d; +- private int e; ++ private int c; public final int getMinuteTimer() { return this.c; } public final void setMinuteTimer(int x) { this.c = x; } // Paper - OBFHELPER ++ private int d; public final int getDayTimer() { return this.d; } public final void setDayTimer(int x) { this.d = x; } // Paper - OBFHELPER ++ private int e; public final int getSpawnChance() { return this.e; } public final void setSpawnChance(int x) { this.e = x; } // Paper - OBFHELPER + + public MobSpawnerTrader(IWorldDataServer iworlddataserver) { + this.b = iworlddataserver; +- this.c = 1200; +- this.d = iworlddataserver.v(); +- this.e = iworlddataserver.w(); +- if (this.d == 0 && this.e == 0) { +- this.d = 24000; +- iworlddataserver.g(this.d); +- this.e = 25; +- iworlddataserver.h(this.e); +- } ++ // Paper start ++ this.setMinuteTimer(Integer.MIN_VALUE); ++ //this.d = iworlddataserver.v(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value ++ //this.e = iworlddataserver.w(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value ++ //if (this.d == 0 && this.e == 0) { ++ // this.d = 24000; ++ // iworlddataserver.g(this.d); ++ // this.e = 25; ++ // iworlddataserver.h(this.e); ++ //} ++ // Paper end + + } + + @Override + public int a(WorldServer worldserver, boolean flag, boolean flag1) { ++ // Paper start ++ if (this.getMinuteTimer() == Integer.MIN_VALUE) { ++ this.setMinuteTimer(worldserver.paperConfig.wanderingTraderSpawnMinuteTicks); ++ this.setDayTimer(worldserver.paperConfig.wanderingTraderSpawnDayTicks); ++ this.setSpawnChance(worldserver.paperConfig.wanderingTraderSpawnChanceMin); ++ } + if (!worldserver.getGameRules().getBoolean(GameRules.DO_TRADER_SPAWNING)) { + return 0; +- } else if (--this.c > 0) { ++ } else if (this.getMinuteTimer() - 1 > 0) { ++ this.setMinuteTimer(this.getMinuteTimer() - 1); + return 0; + } else { +- this.c = 1200; +- this.d -= 1200; +- this.b.g(this.d); +- if (this.d > 0) { ++ this.setMinuteTimer(worldserver.paperConfig.wanderingTraderSpawnMinuteTicks); ++ this.setDayTimer(getDayTimer() - worldserver.paperConfig.wanderingTraderSpawnMinuteTicks); ++ //this.b.g(this.d); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways ++ if (this.getDayTimer() > 0) { + return 0; + } else { +- this.d = 24000; ++ this.setDayTimer(worldserver.paperConfig.wanderingTraderSpawnDayTicks); + if (!worldserver.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING)) { + return 0; + } else { +- int i = this.e; ++ int i = this.getSpawnChance(); + +- this.e = MathHelper.clamp(this.e + 25, 25, 75); +- this.b.h(this.e); ++ this.setSpawnChance(MathHelper.clamp(i + worldserver.paperConfig.wanderingTraderSpawnChanceFailureIncrement, worldserver.paperConfig.wanderingTraderSpawnChanceMin, worldserver.paperConfig.wanderingTraderSpawnChanceMax)); ++ //this.b.h(this.e); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways + if (this.a.nextInt(100) > i) { + return 0; + } else if (this.a(worldserver)) { +- this.e = 25; ++ this.setSpawnChance(worldserver.paperConfig.wanderingTraderSpawnChanceMin); ++ // Paper end + return 1; + } else { + return 0; diff --git a/patches/server-unmapped/0001/0601-Significantly-improve-performance-of-the-end-generat.patch b/patches/server-unmapped/0001/0601-Significantly-improve-performance-of-the-end-generat.patch new file mode 100644 index 0000000000..b205a133b3 --- /dev/null +++ b/patches/server-unmapped/0001/0601-Significantly-improve-performance-of-the-end-generat.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SuperCoder7979 <25208576+SuperCoder7979@users.noreply.github.com> +Date: Tue, 3 Nov 2020 23:48:05 -0600 +Subject: [PATCH] Significantly improve performance of the end generation + +This patch implements a noise cache for the end which significantly reduces the computation time of generation. This results in about a 3x improvement. + +Original code by SuperCoder7979 and Gegy in Lithium, licensed under LGPL-3.0 (Source: https://github.com/jellysquid3/lithium-fabric) + +Co-authored-by: Gegy +Co-authored-by: Dylan Xaldin +Co-authored-by: pop4959 + +diff --git a/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerTheEnd.java b/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerTheEnd.java +index 1077972d694d604c3ec97d333d34a9ab81819c33..62fe4cc00a24e330443d2f006a88dbab3798fb28 100644 +--- a/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerTheEnd.java ++++ b/src/main/java/net/minecraft/world/level/biome/WorldChunkManagerTheEnd.java +@@ -3,10 +3,12 @@ package net.minecraft.world.level.biome; + import com.google.common.collect.ImmutableList; + import com.mojang.serialization.Codec; + import com.mojang.serialization.codecs.RecordCodecBuilder; ++import it.unimi.dsi.fastutil.HashCommon; // Paper + import java.util.List; + import net.minecraft.core.IRegistry; + import net.minecraft.resources.RegistryLookupCodec; + import net.minecraft.util.MathHelper; ++import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.levelgen.SeededRandom; + import net.minecraft.world.level.levelgen.synth.NoiseGenerator3Handler; + +@@ -27,6 +29,16 @@ public class WorldChunkManagerTheEnd extends WorldChunkManager { + private final BiomeBase k; + private final BiomeBase l; + private final BiomeBase m; ++ // Paper start ++ private static final class NoiseCache { ++ public long[] keys = new long[8192]; ++ public float[] values = new float[8192]; ++ public NoiseCache() { ++ java.util.Arrays.fill(keys, Long.MIN_VALUE); ++ } ++ } ++ private static final ThreadLocal> noiseCache = ThreadLocal.withInitial(java.util.WeakHashMap::new); ++ // Paper end + + public WorldChunkManagerTheEnd(IRegistry iregistry, long i) { + this(iregistry, i, (BiomeBase) iregistry.d(Biomes.THE_END), (BiomeBase) iregistry.d(Biomes.END_HIGHLANDS), (BiomeBase) iregistry.d(Biomes.END_MIDLANDS), (BiomeBase) iregistry.d(Biomes.SMALL_END_ISLANDS), (BiomeBase) iregistry.d(Biomes.END_BARRENS)); +@@ -81,13 +93,27 @@ public class WorldChunkManagerTheEnd extends WorldChunkManager { + + f = MathHelper.a(f, -100.0F, 80.0F); + ++ NoiseCache cache = noiseCache.get().computeIfAbsent(noisegenerator3handler, m -> new NoiseCache()); // Paper + for (int k1 = -12; k1 <= 12; ++k1) { + for (int l1 = -12; l1 <= 12; ++l1) { + long i2 = (long) (k + k1); + long j2 = (long) (l + l1); + +- if (i2 * i2 + j2 * j2 > 4096L && noisegenerator3handler.a((double) i2, (double) j2) < -0.8999999761581421D) { +- float f1 = (MathHelper.e((float) i2) * 3439.0F + MathHelper.e((float) j2) * 147.0F) % 13.0F + 9.0F; ++ // Paper start - Significantly improve end generation performance by using a noise cache ++ long key = ChunkCoordIntPair.pair((int) i2, (int) j2); ++ int index = (int) HashCommon.mix(key) & 8191; ++ float f1 = Float.MIN_VALUE; ++ if (cache.keys[index] == key) { ++ f1 = cache.values[index]; ++ } else { ++ if (i2 * i2 + j2 * j2 > 4096L && noisegenerator3handler.a((double) i2, (double) j2) < -0.8999999761581421D) { ++ f1 = (MathHelper.e((float) i2) * 3439.0F + MathHelper.e((float) j2) * 147.0F) % 13.0F + 9.0F; ++ } ++ cache.keys[index] = key; ++ cache.values[index] = f1; ++ } ++ if (f1 != Float.MIN_VALUE) { ++ // Paper end + float f2 = (float) (i1 - k1 * 2); + float f3 = (float) (j1 - l1 * 2); + float f4 = 100.0F - MathHelper.c(f2 * f2 + f3 * f3) * f1; diff --git a/patches/server-unmapped/0001/0602-Expose-world-spawn-angle.patch b/patches/server-unmapped/0001/0602-Expose-world-spawn-angle.patch new file mode 100644 index 0000000000..e59a3f5096 --- /dev/null +++ b/patches/server-unmapped/0001/0602-Expose-world-spawn-angle.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Tue, 17 Nov 2020 19:13:09 +0200 +Subject: [PATCH] Expose world spawn angle + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 61513e3c27cf95090e61f7085bb2f0435c774df9..a7ca1dbebf30262d7c526f96b7a45482b1f898f4 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -894,7 +894,7 @@ public abstract class PlayerList { + if (location == null) { + worldserver1 = this.server.getWorldServer(World.OVERWORLD); + blockposition = entityplayer1.getSpawnPoint(worldserver1); +- location = new Location(worldserver1.getWorld(), (double) ((float) blockposition.getX() + 0.5F), (double) ((float) blockposition.getY() + 0.1F), (double) ((float) blockposition.getZ() + 0.5F)); ++ location = new Location(worldserver1.getWorld(), (double) ((float) blockposition.getX() + 0.5F), (double) ((float) blockposition.getY() + 0.1F), (double) ((float) blockposition.getZ() + 0.5F), worldserver1.worldData.getSpawnAngle(), 0.0F); // Paper - use world spawn angle + } + + Player respawnPlayer = cserver.getPlayer(entityplayer1); +diff --git a/src/main/java/net/minecraft/world/level/storage/WorldData.java b/src/main/java/net/minecraft/world/level/storage/WorldData.java +index 81ad90ba93481decdfaa38fc9fa81bca0e402781..7599488f7d4b168c92078c2d2987cb38f0dee8a9 100644 +--- a/src/main/java/net/minecraft/world/level/storage/WorldData.java ++++ b/src/main/java/net/minecraft/world/level/storage/WorldData.java +@@ -12,6 +12,7 @@ public interface WorldData { + + int c(); + ++ default float getSpawnAngle() { return d(); } // Paper - OBFHELPER + float d(); + + long getTime(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index a824350ff95d6193c0c10e21ba8c3407c7a44dd1..ac0fc981c1d6a9e75a062363535630ebf4937840 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -380,7 +380,7 @@ public class CraftWorld implements World { + @Override + public Location getSpawnLocation() { + BlockPosition spawn = world.getSpawn(); +- return new Location(this, spawn.getX(), spawn.getY(), spawn.getZ()); ++ return new Location(this, spawn.getX(), spawn.getY(), spawn.getZ(), world.worldData.getSpawnAngle(), 0.0F); // Paper - expose world spawn angle + } + + @Override diff --git a/patches/server-unmapped/0001/0603-Add-Destroy-Speed-API.patch b/patches/server-unmapped/0001/0603-Add-Destroy-Speed-API.patch new file mode 100644 index 0000000000..bdeb073a85 --- /dev/null +++ b/patches/server-unmapped/0001/0603-Add-Destroy-Speed-API.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ineusia +Date: Mon, 26 Oct 2020 11:48:06 -0500 +Subject: [PATCH] Add Destroy Speed API + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index f1f03bd0162a158761f300cbd96aa32101542abb..fab9e1e5d63c22faceae093dc88769d203d359c6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -769,5 +769,23 @@ public class CraftBlock implements Block { + public String getTranslationKey() { + return org.bukkit.Bukkit.getUnsafe().getTranslationKey(this); + } ++ ++ @Override ++ public float getDestroySpeed(ItemStack itemStack, boolean considerEnchants) { ++ net.minecraft.world.item.ItemStack nmsItemStack; ++ if (itemStack instanceof CraftItemStack) { ++ nmsItemStack = ((CraftItemStack) itemStack).getHandle(); ++ } else { ++ nmsItemStack = CraftItemStack.asNMSCopy(itemStack); ++ } ++ float speed = nmsItemStack.getItem().getDestroySpeed(nmsItemStack, this.getNMSBlock().getBlockData()); ++ if (speed > 1.0F && considerEnchants) { ++ int enchantLevel = net.minecraft.world.item.enchantment.EnchantmentManager.getEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.DIG_SPEED, nmsItemStack); ++ if (enchantLevel > 0) { ++ speed += enchantLevel * enchantLevel + 1; ++ } ++ } ++ return speed; ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0604-Fix-Player-spawnParticle-x-y-z-precision-loss.patch b/patches/server-unmapped/0001/0604-Fix-Player-spawnParticle-x-y-z-precision-loss.patch new file mode 100644 index 0000000000..ffecbacf4d --- /dev/null +++ b/patches/server-unmapped/0001/0604-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 204d3e879c736f44e01f9246af26f6cccf8d72a7..5a245184d6290a58bb9aed139cc4c7b5511ce491 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2014,7 +2014,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + if (data != null && !particle.getDataType().isInstance(data)) { + throw new IllegalArgumentException("data should be " + particle.getDataType() + " got " + data.getClass()); + } +- PacketPlayOutWorldParticles packetplayoutworldparticles = new PacketPlayOutWorldParticles(CraftParticle.toNMS(particle, data), true, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); ++ PacketPlayOutWorldParticles packetplayoutworldparticles = new PacketPlayOutWorldParticles(CraftParticle.toNMS(particle, data), true, x, y, z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); // Paper - Fix x/y/z coordinate precision loss + getHandle().playerConnection.sendPacket(packetplayoutworldparticles); + + } diff --git a/patches/server-unmapped/0001/0605-Add-LivingEntity-clearActiveItem.patch b/patches/server-unmapped/0001/0605-Add-LivingEntity-clearActiveItem.patch new file mode 100644 index 0000000000..7856335fa1 --- /dev/null +++ b/patches/server-unmapped/0001/0605-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 090e0931df410526cb7b0aab196a01f57ffbb285..5d2ed8a0cf8351df1c8b2946f8a614fe13c34673 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -785,6 +785,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return getHandle().activeItem.asBukkitMirror(); + } + ++ // Paper start ++ @Override ++ public void clearActiveItem() { ++ getHandle().clearActiveItem(); ++ } ++ // Paper end ++ + @Override + public int getItemUseRemainingTime() { + return getHandle().getItemUseRemainingTime(); diff --git a/patches/server-unmapped/0001/0606-Add-PlayerItemCooldownEvent.patch b/patches/server-unmapped/0001/0606-Add-PlayerItemCooldownEvent.patch new file mode 100644 index 0000000000..81581680ce --- /dev/null +++ b/patches/server-unmapped/0001/0606-Add-PlayerItemCooldownEvent.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KennyTV +Date: Tue, 25 Aug 2020 13:48:33 +0200 +Subject: [PATCH] Add PlayerItemCooldownEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ItemCooldownPlayer.java b/src/main/java/net/minecraft/world/item/ItemCooldownPlayer.java +index 5f70e39f4da2880a6f734a225be83061b00847c8..ef9ffad211473f1cbcd0b8fd200ff04ec2051f94 100644 +--- a/src/main/java/net/minecraft/world/item/ItemCooldownPlayer.java ++++ b/src/main/java/net/minecraft/world/item/ItemCooldownPlayer.java +@@ -3,14 +3,26 @@ package net.minecraft.world.item; + import net.minecraft.network.protocol.game.PacketPlayOutSetCooldown; + import net.minecraft.server.level.EntityPlayer; + ++import io.papermc.paper.event.player.PlayerItemCooldownEvent; // Paper ++ + public class ItemCooldownPlayer extends ItemCooldown { + +- private final EntityPlayer a; ++ private final EntityPlayer a; public EntityPlayer getEntityPlayer() { return a; } // Paper - OBFHELPER + + public ItemCooldownPlayer(EntityPlayer entityplayer) { + this.a = entityplayer; + } + ++ // Paper start ++ @Override ++ public void setCooldown(Item item, int ticks) { ++ PlayerItemCooldownEvent event = new PlayerItemCooldownEvent(getEntityPlayer().getBukkitEntity(), org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(item), ticks); ++ if (event.callEvent()) { ++ super.setCooldown(item, event.getCooldown()); ++ } ++ } ++ // Paper end ++ + @Override + protected void b(Item item, int i) { + super.b(item, i); diff --git a/patches/server-unmapped/0001/0607-More-lightning-API.patch b/patches/server-unmapped/0001/0607-More-lightning-API.patch new file mode 100644 index 0000000000..e08b3251e5 --- /dev/null +++ b/patches/server-unmapped/0001/0607-More-lightning-API.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KennyTV +Date: Sun, 26 Jul 2020 14:44:09 +0200 +Subject: [PATCH] More lightning API + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityLightning.java b/src/main/java/net/minecraft/world/entity/EntityLightning.java +index 834ced9d9b385c8f1d66355244313d62a97d9c98..85f571a791bce63989890f277857bc7bdeec0cb5 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLightning.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLightning.java +@@ -31,7 +31,7 @@ public class EntityLightning extends Entity { + + private int lifeTicks; + public long b; +- private int d; ++ private int d; public int getFlashCount() { return d; } public void setFlashCount(int flashes) { this.d = flashes; } // Paper - OBFHELPER + public boolean isEffect; + @Nullable + private EntityPlayer f; +@@ -49,6 +49,16 @@ public class EntityLightning extends Entity { + this.isEffect = flag; + } + ++ // Paper start ++ public int getLifeTicks() { ++ return lifeTicks; ++ } ++ ++ public void setLifeTicks(int lifeTicks) { ++ this.lifeTicks = lifeTicks; ++ } ++ // Paper end ++ + @Override + public SoundCategory getSoundCategory() { + return SoundCategory.WEATHER; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +index 3e054592cef6526b769d14460a671ea78a5ef58a..395f702b14425d85ff3a7938c32f7bfd5523f3ca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +@@ -45,4 +45,27 @@ public class CraftLightningStrike extends CraftEntity implements LightningStrike + return spigot; + } + // Spigot end ++ ++ // Paper start ++ @Override ++ public int getFlashCount() { ++ return getHandle().getFlashCount(); ++ } ++ ++ @Override ++ public void setFlashCount(int flashes) { ++ com.google.common.base.Preconditions.checkArgument(flashes >= 0, "Flashes has to be a positive number!"); ++ getHandle().setFlashCount(flashes); ++ } ++ ++ @Override ++ public int getLifeTicks() { ++ return getHandle().getLifeTicks(); ++ } ++ ++ @Override ++ public void setLifeTicks(int lifeTicks) { ++ getHandle().setLifeTicks(lifeTicks); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0608-Climbing-should-not-bypass-cramming-gamerule.patch b/patches/server-unmapped/0001/0608-Climbing-should-not-bypass-cramming-gamerule.patch new file mode 100644 index 0000000000..4c501b7baa --- /dev/null +++ b/patches/server-unmapped/0001/0608-Climbing-should-not-bypass-cramming-gamerule.patch @@ -0,0 +1,179 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index b4d76494851601d61a69e2f060727a68f4461267..6262246c4018c660705fbad028f297fc44e7197f 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -710,4 +710,9 @@ public class PaperWorldConfig { + wanderingTraderSpawnChanceMin = getInt("wandering-trader.spawn-chance-min", wanderingTraderSpawnChanceMin); + wanderingTraderSpawnChanceMax = getInt("wandering-trader.spawn-chance-max", wanderingTraderSpawnChanceMax); + } ++ ++ public boolean fixClimbingBypassingCrammingRule = false; ++ private void fixClimbingBypassingCrammingRule() { ++ fixClimbingBypassingCrammingRule = getBoolean("fix-climbing-bypassing-cramming-rule", fixClimbingBypassingCrammingRule); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 2a3025793db52a18e58f832c9da78c6c3b39d33c..d801486565865cf3b56e9d80b7c1643e9b0f4597 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1575,6 +1575,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public boolean isCollidable() { ++ // Paper start ++ return isCollidable(false); ++ } ++ ++ public boolean isCollidable(boolean ignoreClimbing) { ++ // Paper end + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index a5b731bab16bf05351fee7e64ab6ae4d830309f7..358bb6244b7b9e785c7dcc3725ee00cfbb917cec 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -137,7 +137,6 @@ import org.bukkit.event.entity.EntityTeleportEvent; + import org.bukkit.event.player.PlayerItemConsumeEvent; + // CraftBukkit end + +-import co.aikar.timings.MinecraftTimings; // Paper + + public abstract class EntityLiving extends Entity { + +@@ -2959,7 +2958,7 @@ public abstract class EntityLiving extends Entity { + return; + } + // Paper - end don't run getEntities if we're not going to use its result +- List list = this.world.getEntities(this, this.getBoundingBox(), IEntitySelector.a(this)); ++ List list = this.world.getEntities(this, this.getBoundingBox(), IEntitySelector.pushable(this, world.paperConfig.fixClimbingBypassingCrammingRule)); // Paper - fix climbing bypassing cramming rule + + if (!list.isEmpty()) { + // Paper - move up +@@ -3097,9 +3096,16 @@ public abstract class EntityLiving extends Entity { + return !this.dead && this.collides; // CraftBukkit + } + ++ // Paper start + @Override + public boolean isCollidable() { +- return this.isAlive() && !this.isSpectator() && !this.isClimbing() && this.collides; // CraftBukkit ++ return this.isCollidable(world.paperConfig.fixClimbingBypassingCrammingRule); ++ } ++ ++ @Override ++ public boolean isCollidable(boolean ignoreClimbing) { ++ return this.isAlive() && !this.isSpectator() && (ignoreClimbing || !this.isClimbing()) && this.collides; // CraftBukkit ++ // Paper end + } + + // CraftBukkit start - collidable API +diff --git a/src/main/java/net/minecraft/world/entity/IEntitySelector.java b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +index c8d7ea8cfa4945af4a6675172b931a4cc3ca2801..f5e32faeb6d937cf90b1f3ea251b5cfc91f2338d 100644 +--- a/src/main/java/net/minecraft/world/entity/IEntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/IEntitySelector.java +@@ -51,11 +51,17 @@ public final class IEntitySelector { + } + + public static Predicate a(Entity entity) { ++ // Paper start - ignoreClimbing param ++ return pushable(entity, false); ++ } ++ ++ public static Predicate pushable(Entity entity, boolean ignoreClimbing) { ++ // Paper end + ScoreboardTeamBase scoreboardteambase = entity.getScoreboardTeam(); + ScoreboardTeamBase.EnumTeamPush scoreboardteambase_enumteampush = scoreboardteambase == null ? ScoreboardTeamBase.EnumTeamPush.ALWAYS : scoreboardteambase.getCollisionRule(); + + return (Predicate) (scoreboardteambase_enumteampush == ScoreboardTeamBase.EnumTeamPush.NEVER ? Predicates.alwaysFalse() : IEntitySelector.g.and((entity1) -> { +- if (!entity1.canCollideWith(entity) || !entity.canCollideWith(entity1)) { // CraftBukkit - collidable API ++ if (!entity1.isCollidable(ignoreClimbing) || !entity1.canCollideWith(entity) || !entity.canCollideWith(entity1)) { // CraftBukkit - collidable API // Paper - isCollidable + return false; + } else if (entity.world.isClientSide && (!(entity1 instanceof EntityHuman) || !((EntityHuman) entity1).ez())) { + return false; +diff --git a/src/main/java/net/minecraft/world/entity/ambient/EntityBat.java b/src/main/java/net/minecraft/world/entity/ambient/EntityBat.java +index 2167590677c68d261b804a16853bd943f16a76dd..61ebb278cf4ef57ae7a86c6c6ef1fa14559f21e2 100644 +--- a/src/main/java/net/minecraft/world/entity/ambient/EntityBat.java ++++ b/src/main/java/net/minecraft/world/entity/ambient/EntityBat.java +@@ -76,7 +76,7 @@ public class EntityBat extends EntityAmbient { + } + + @Override +- public boolean isCollidable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityParrot.java b/src/main/java/net/minecraft/world/entity/animal/EntityParrot.java +index e93375171462b95e270230f5f72f7eb5c6b0ff58..699dd0ac1f8d0d340ab1a560106336fc7cc95d5b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityParrot.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityParrot.java +@@ -368,8 +368,8 @@ public class EntityParrot extends EntityPerchable implements EntityBird { + } + + @Override +- public boolean isCollidable() { +- return super.isCollidable(); // CraftBukkit - collidable API ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper ++ return super.isCollidable(ignoreClimbing); // CraftBukkit - collidable API // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java +index d05391290dc11440aae5f38328a0530c500ac601..d678e3164ecdb7f0c600597bcb39d1054dfbc4b2 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java +@@ -227,7 +227,7 @@ public abstract class EntityHorseAbstract extends EntityAnimal implements IInven + } + + @Override +- public boolean isCollidable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return !this.isVehicle(); + } + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +index 5eb900619951083b9a777b1645cb5495b99968ec..c0e0750adef0ae6aff7635c84f6585f06c5fc38d 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java +@@ -362,7 +362,7 @@ public class EntityArmorStand extends EntityLiving { + } + + @Override +- public boolean isCollidable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java +index 25a6f53888293994a50128bcbcbb88ff141c5395..add2aef0192a3b3767c1e477145978b9702c0fb4 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java +@@ -150,7 +150,7 @@ public class EntityBoat extends Entity { + } + + @Override +- public boolean isCollidable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return true; + } + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java +index 7d91e6b75a8a827853b0ca8e53b8ec19e2cf1092..2e3ceab3e34f7756764b3471b13d48d1263ecba9 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java +@@ -145,7 +145,7 @@ public abstract class EntityMinecartAbstract extends Entity { + } + + @Override +- public boolean isCollidable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return true; + } + diff --git a/patches/server-unmapped/0001/0609-Added-missing-default-perms-for-commands.patch b/patches/server-unmapped/0001/0609-Added-missing-default-perms-for-commands.patch new file mode 100644 index 0000000000..25843d00d1 --- /dev/null +++ b/patches/server-unmapped/0001/0609-Added-missing-default-perms-for-commands.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 16 Nov 2020 12:01:52 -0800 +Subject: [PATCH] Added missing default perms for commands + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java +index d5f4ece060b61de9ca5292d1f2411c709de5ece2..f0a57d225b81a505ff12425155ba838d8fad990c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java +@@ -31,6 +31,59 @@ public final class CommandPermissions { + DefaultPermissions.registerPermission(PREFIX + "effect", "Allows the user to add/remove effects on players", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "selector", "Allows the use of selectors", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "trigger", "Allows the use of the trigger command", PermissionDefault.TRUE, commands); ++ // Paper start ++ DefaultPermissions.registerPermission(PREFIX + "attribute", "Allows the user to query, add, remove or set an entity attribute", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "advancement", "Allows the user to give, remove, or check player advancements", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "ban", "Allows the user to add players to banlist", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "ban-ip", "Allows the user to add ip address to banlist", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "banlist", "Allows the user to display banlist", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "bossbar", "Allows the user to create and modify bossbars", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "clear", "Allows the user to clear items from player inventory", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "clone", "Allows the user to copy blocks from one place to another", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "data", "Allows the user to get, merge, modify, and remove block entity and entity NBT data", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "datapack", "Allows the user to control loaded data packs", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "debug", "Allows the user to start or stop a debugging session", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "deop", "Allows the user to revoke operator status from a player", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "difficulty", "Allows the user to set the difficulty level", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "enchant", "Allows the user to enchant a player item", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "execute", "Allows the user to execute another command", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "fill", "Allows the user to fill a region with a specific block", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "forceload", "Allows the user to force chunks to be constantly loaded or not", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "function", "Allows the user to run a function", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "gamerule", "Allows a user to set or query a game rule value", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "locate", "Allows the user to locate the closest structure", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "locatebiome", "Allows the user to locate the closest biome", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "loot", "Allows the user to drop items from an inventory slot onto the ground", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "op", "Allows the user to grant operator status to a player", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "pardon", "Allows the user to remove entries from the banlist", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "particle", "Allows the user to create particles", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "playsound", "Allows the user to play a sound", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "recipe", "Allows the user to give or take recipes", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "reload", "Allows the user to reload loot tables, advancements, and functions from disk", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "replaceitem", "Allows the user to replace items in inventories", PermissionDefault.OP, commands); // Remove in 1.17 (replaced by /item) ++ DefaultPermissions.registerPermission(PREFIX + "save-all", "Allows the user to save the server to disk", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "save-off", "Allows the user disable automatic server saves", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "save-on", "Allows the user enable automatic server saves", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "schedule", "Allows the user to delay the execution of a function", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "scoreboard", "Allows the user manage scoreboard objectives and players", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "setblock", "Allows the user to change a block to another block", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "setidletimeout", "Allows the user to set the time before idle players are kicked", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "setworldspawn", "Allows the user to set the world spawn", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "spawnpoint", "Allows the user to set the spawn point for a player", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "spectate", "Allows the user to make one player in spectator mode spectate an entity", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "spreadplayers", "Allows the user to teleport entities to random locations", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "stopsound", "Allows the user to stop a sound", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "summon", "Allows the user to summon an entity", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "tag", "Allows the user to control entity tags", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "team", "Allows the user to control teams", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "teammsg", "Allows the user to specify the message to send to team", PermissionDefault.TRUE, commands); ++ DefaultPermissions.registerPermission(PREFIX + "tellraw", "Allows the user to display a JSON message to players", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "time", "Allows the user to change or query the world's game time", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "title", "Allows the user to manage screen titles", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "weather", "Allows the user to set the weather", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "whitelist", "Allows the user to manage the server whitelist", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(PREFIX + "worldborder", "Allows the user to manage the world border", PermissionDefault.OP, commands); ++ // Paper end + + DefaultPermissions.registerPermission("minecraft.admin.command_feedback", "Receive command broadcasts when sendCommandFeedback is true", PermissionDefault.OP, commands); + diff --git a/patches/server-unmapped/0001/0610-Add-PlayerShearBlockEvent.patch b/patches/server-unmapped/0001/0610-Add-PlayerShearBlockEvent.patch new file mode 100644 index 0000000000..b63c910557 --- /dev/null +++ b/patches/server-unmapped/0001/0610-Add-PlayerShearBlockEvent.patch @@ -0,0 +1,109 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Thu, 27 Aug 2020 15:02:48 -0400 +Subject: [PATCH] Add PlayerShearBlockEvent + + +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 768934fa4158a9773d06f5b23bfb19db75f6d179..596b4597313b87296d39027b13555b5ad1cba9e6 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -262,7 +262,7 @@ public class Block extends BlockBase implements IMaterial { + + } + +- public static void a(World world, BlockPosition blockposition, ItemStack itemstack) { ++ public static void a(World world, BlockPosition blockposition, ItemStack itemstack) { dropItem(world, blockposition, itemstack); } public static void dropItem(World world, BlockPosition blockposition, ItemStack itemstack) { // Paper - OBFHELPER + if (!world.isClientSide && !itemstack.isEmpty() && world.getGameRules().getBoolean(GameRules.DO_TILE_DROPS)) { + float f = 0.5F; + double d0 = (double) (world.random.nextFloat() * 0.5F) + 0.25D; +diff --git a/src/main/java/net/minecraft/world/level/block/BlockBeehive.java b/src/main/java/net/minecraft/world/level/block/BlockBeehive.java +index ff521e64384281fc88b78bb908d19049743cf63b..828d231a963f1962d88fe170ac86590d86e1df40 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockBeehive.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockBeehive.java +@@ -1,5 +1,7 @@ + package net.minecraft.world.level.block; + ++import io.papermc.paper.event.block.PlayerShearBlockEvent; // Paper - PlayerShearBlockEvent namespace conflicts ++ + import java.util.Iterator; + import java.util.List; + import java.util.Random; +@@ -10,6 +12,7 @@ import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; + import net.minecraft.nbt.NBTBase; + import net.minecraft.nbt.NBTTagCompound; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.sounds.SoundCategory; + import net.minecraft.sounds.SoundEffects; +@@ -116,8 +119,19 @@ public class BlockBeehive extends BlockTileEntity { + + if (i >= 5) { + if (itemstack.getItem() == Items.SHEARS) { ++ // Paper start - Add PlayerShearBlockEvent ++ PlayerShearBlockEvent event = new PlayerShearBlockEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), MCUtil.toBukkitBlock(world, blockposition), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (enumhand == EnumHand.OFF_HAND ? org.bukkit.inventory.EquipmentSlot.OFF_HAND : org.bukkit.inventory.EquipmentSlot.HAND), new java.util.ArrayList<>()); ++ event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.HONEYCOMB, 3))); ++ if (!event.callEvent()) { ++ return EnumInteractionResult.PASS; ++ } ++ // Paper end + world.playSound(entityhuman, entityhuman.locX(), entityhuman.locY(), entityhuman.locZ(), SoundEffects.BLOCK_BEEHIVE_SHEAR, SoundCategory.NEUTRAL, 1.0F, 1.0F); +- a(world, blockposition); ++ // Paper start - Add PlayerShearBlockEvent ++ for (org.bukkit.inventory.ItemStack item : event.getDrops()) { ++ dropItem(world, blockposition, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item)); ++ } ++ // Paper end + itemstack.damage(1, entityhuman, (entityhuman1) -> { + entityhuman1.broadcastItemBreak(enumhand); + }); +diff --git a/src/main/java/net/minecraft/world/level/block/BlockPumpkin.java b/src/main/java/net/minecraft/world/level/block/BlockPumpkin.java +index 130b768ac7155c2960694dfa12163e46315f7797..11a9c3e76baaa50317a3cd7d81541a3666028c16 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockPumpkin.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockPumpkin.java +@@ -2,6 +2,7 @@ package net.minecraft.world.level.block; + + import net.minecraft.core.BlockPosition; + import net.minecraft.core.EnumDirection; ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundCategory; + import net.minecraft.sounds.SoundEffects; + import net.minecraft.world.EnumHand; +@@ -15,6 +16,8 @@ import net.minecraft.world.level.block.state.BlockBase; + import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.phys.MovingObjectPositionBlock; + ++import io.papermc.paper.event.block.PlayerShearBlockEvent; // Paper - PlayerShearBlockEvent namespace conflicts ++ + public class BlockPumpkin extends BlockStemmed { + + protected BlockPumpkin(BlockBase.Info blockbase_info) { +@@ -27,15 +30,26 @@ public class BlockPumpkin extends BlockStemmed { + + if (itemstack.getItem() == Items.SHEARS) { + if (!world.isClientSide) { ++ // Paper start - Add PlayerShearBlockEvent ++ PlayerShearBlockEvent event = new PlayerShearBlockEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), MCUtil.toBukkitBlock(world, blockposition), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (enumhand == EnumHand.OFF_HAND ? org.bukkit.inventory.EquipmentSlot.OFF_HAND : org.bukkit.inventory.EquipmentSlot.HAND), new java.util.ArrayList<>()); ++ event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.PUMPKIN_SEEDS, 4))); ++ if (!event.callEvent()) { ++ return EnumInteractionResult.PASS; ++ } ++ // Paper end + EnumDirection enumdirection = movingobjectpositionblock.getDirection(); + EnumDirection enumdirection1 = enumdirection.n() == EnumDirection.EnumAxis.Y ? entityhuman.getDirection().opposite() : enumdirection; + + world.playSound((EntityHuman) null, blockposition, SoundEffects.BLOCK_PUMPKIN_CARVE, SoundCategory.BLOCKS, 1.0F, 1.0F); + world.setTypeAndData(blockposition, (IBlockData) Blocks.CARVED_PUMPKIN.getBlockData().set(BlockPumpkinCarved.a, enumdirection1), 11); +- EntityItem entityitem = new EntityItem(world, (double) blockposition.getX() + 0.5D + (double) enumdirection1.getAdjacentX() * 0.65D, (double) blockposition.getY() + 0.1D, (double) blockposition.getZ() + 0.5D + (double) enumdirection1.getAdjacentZ() * 0.65D, new ItemStack(Items.PUMPKIN_SEEDS, 4)); ++ // Paper start - Add PlayerShearBlockEvent ++ for (org.bukkit.inventory.ItemStack item : event.getDrops()) { ++ EntityItem entityitem = new EntityItem(world, (double) blockposition.getX() + 0.5D + (double) enumdirection1.getAdjacentX() * 0.65D, (double) blockposition.getY() + 0.1D, (double) blockposition.getZ() + 0.5D + (double) enumdirection1.getAdjacentZ() * 0.65D, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item)); ++ // Paper end + + entityitem.setMot(0.05D * (double) enumdirection1.getAdjacentX() + world.random.nextDouble() * 0.02D, 0.05D, 0.05D * (double) enumdirection1.getAdjacentZ() + world.random.nextDouble() * 0.02D); + world.addEntity(entityitem); ++ } // Paper - Add PlayerShearBlockEvent + itemstack.damage(1, entityhuman, (entityhuman1) -> { + entityhuman1.broadcastItemBreak(enumhand); + }); diff --git a/patches/server-unmapped/0001/0611-Add-warning-for-servers-not-running-on-Java-11.patch b/patches/server-unmapped/0001/0611-Add-warning-for-servers-not-running-on-Java-11.patch new file mode 100644 index 0000000000..a10fd0e94b --- /dev/null +++ b/patches/server-unmapped/0001/0611-Add-warning-for-servers-not-running-on-Java-11.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kyle Wood +Date: Wed, 2 Dec 2020 21:58:45 -0800 +Subject: [PATCH] Add warning for servers not running on Java 11 + + +diff --git a/src/main/java/io/papermc/paper/util/PaperJvmChecker.java b/src/main/java/io/papermc/paper/util/PaperJvmChecker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c6ea429819c07e7f4bc257cad73463a030767825 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/PaperJvmChecker.java +@@ -0,0 +1,48 @@ ++package io.papermc.paper.util; ++ ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++public class PaperJvmChecker { ++ ++ private static int getJvmVersion() { ++ String javaVersion = System.getProperty("java.version"); ++ final Matcher matcher = Pattern.compile("(?:1\\.)?(\\d+)").matcher(javaVersion); ++ if (!matcher.find()) { ++ LogManager.getLogger().warn("Failed to determine Java version; Could not parse: {}", javaVersion); ++ return -1; ++ } ++ ++ final String version = matcher.group(1); ++ try { ++ return Integer.parseInt(version); ++ } catch (final NumberFormatException e) { ++ LogManager.getLogger().warn("Failed to determine Java version; Could not parse {} from {}", version, javaVersion, e); ++ return -1; ++ } ++ } ++ ++ public static void checkJvm() { ++ if (getJvmVersion() < 11) { ++ final Logger logger = LogManager.getLogger(); ++ logger.warn("************************************************************"); ++ logger.warn("* WARNING - YOU ARE RUNNING AN OUTDATED VERSION OF JAVA."); ++ logger.warn("* PAPER WILL STOP BEING COMPATIBLE WITH THIS VERSION OF"); ++ logger.warn("* JAVA WHEN MINECRAFT 1.17 IS RELEASED."); ++ logger.warn("*"); ++ logger.warn("* Please update the version of Java you use to run Paper"); ++ logger.warn("* to at least Java 11. When Paper for Minecraft 1.17 is"); ++ logger.warn("* released support for versions of Java before 11 will"); ++ logger.warn("* be dropped."); ++ logger.warn("*"); ++ logger.warn("* Current Java version: {}", System.getProperty("java.version")); ++ logger.warn("*"); ++ logger.warn("* Check this forum post for more information: "); ++ logger.warn("* https://papermc.io/java11"); ++ logger.warn("************************************************************"); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 43b713476c6f841aafaac9f2a1216b937261efc2..07021c760b0d734b91240ca96c6480be469a1005 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -181,6 +181,7 @@ import org.bukkit.event.server.ServerLoadEvent; + + import co.aikar.timings.MinecraftTimings; // Paper + import org.spigotmc.SlackActivityAccountant; // Spigot ++import io.papermc.paper.util.PaperJvmChecker; // Paper + + public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant implements IMojangStatistics, ICommandListener, AutoCloseable { + +@@ -1075,6 +1076,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Wed, 2 Dec 2020 20:17:54 -0800 +Subject: [PATCH] Set spigots verbose world setting to false by def + + +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 6b015c1f26facb4e82d75b252164dec05731ca6c..094a934c168d232b0550c3efe722f2ebfbdf8e24 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -20,7 +20,7 @@ public class SpigotWorldConfig + + public void init() + { +- this.verbose = getBoolean( "verbose", true ); ++ this.verbose = getBoolean( "verbose", false ); // Paper + + log( "-------- World Settings For [" + worldName + "] --------" ); + SpigotConfig.readConfig( SpigotWorldConfig.class, this ); diff --git a/patches/server-unmapped/0001/0613-Fix-curing-zombie-villager-discount-exploit.patch b/patches/server-unmapped/0001/0613-Fix-curing-zombie-villager-discount-exploit.patch new file mode 100644 index 0000000000..638e510669 --- /dev/null +++ b/patches/server-unmapped/0001/0613-Fix-curing-zombie-villager-discount-exploit.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 8 Dec 2020 20:14:20 -0600 +Subject: [PATCH] Fix curing zombie villager discount exploit + +This fixes the exploit used to gain absurd trading discounts with infecting +and curing a villager on repeat by simply resetting the relevant part of +the reputation when it is cured. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 6262246c4018c660705fbad028f297fc44e7197f..9ebe8771c2d5e843756868824740ef599ca8455f 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -715,4 +715,9 @@ public class PaperWorldConfig { + private void fixClimbingBypassingCrammingRule() { + fixClimbingBypassingCrammingRule = getBoolean("fix-climbing-bypassing-cramming-rule", fixClimbingBypassingCrammingRule); + } ++ ++ public boolean fixCuringZombieVillagerDiscountExploit = true; ++ private void fixCuringExploit() { ++ fixCuringZombieVillagerDiscountExploit = getBoolean("game-mechanics.fix-curing-zombie-villager-discount-exploit", fixCuringZombieVillagerDiscountExploit); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java b/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java +index 7d34d1157786227ac210edc1595a024ccb61a3e9..ce8a4cc9f642a740947c4e63d6eb78ad93a0fd44 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/Reputation.java +@@ -223,6 +223,7 @@ public class Reputation { + + } + ++ public final void removeReputationForType(ReputationType reputationType) { this.b(reputationType); } // Paper - OBFHELPER + public void b(ReputationType reputationtype) { + this.a.removeInt(reputationtype); + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +index 382059b2c2b6a7b8231b3c3a5c9a401091b1ad18..2d0b83923d58cc7b6918b4e2ff2bece13ca26899 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +@@ -1007,6 +1007,15 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + @Override + public void a(ReputationEvent reputationevent, Entity entity) { + if (reputationevent == ReputationEvent.a) { ++ // Paper start - fix MC-181190 ++ if (world.paperConfig.fixCuringZombieVillagerDiscountExploit) { ++ final Reputation.a playerReputation = this.getReputation().getReputations().get(entity.getUniqueID()); ++ if (playerReputation != null) { ++ playerReputation.removeReputationForType(ReputationType.MAJOR_POSITIVE); ++ playerReputation.removeReputationForType(ReputationType.MINOR_POSITIVE); ++ } ++ } ++ // Paper end + this.by.a(entity.getUniqueID(), ReputationType.MAJOR_POSITIVE, 20); + this.by.a(entity.getUniqueID(), ReputationType.MINOR_POSITIVE, 25); + } else if (reputationevent == ReputationEvent.e) { diff --git a/patches/server-unmapped/0001/0614-Limit-recipe-packets.patch b/patches/server-unmapped/0001/0614-Limit-recipe-packets.patch new file mode 100644 index 0000000000..9964fcfeeb --- /dev/null +++ b/patches/server-unmapped/0001/0614-Limit-recipe-packets.patch @@ -0,0 +1,74 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 7d50aded88f5b7dfebaea1aebc86231f7b5c4e25..652d87fc5d566dba8018c81676329f0e0bca471b 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -334,6 +334,13 @@ public class PaperConfig { + tabSpamLimit = getInt("settings.spam-limiter.tab-spam-limit", tabSpamLimit); + } + ++ public static int autoRecipeIncrement = 1; ++ public static int autoRecipeLimit = 20; ++ private static void autoRecipieLimiters() { ++ autoRecipeIncrement = getInt("settings.spam-limiter.recipe-spam-increment", autoRecipeIncrement); ++ autoRecipeLimit = getInt("settings.spam-limiter.recipe-spam-limit", autoRecipeLimit); ++ } ++ + public static boolean velocitySupport; + public static boolean velocityOnlineMode; + public static byte[] velocitySecretKey; +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index d00c17210b3c0aff40b37ff11f8e9dc6ae1ba948..79c0ef65f3a1d28de73afb3f1891cfc1a0a3dd90 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.network; + ++import com.destroystokyo.paper.PaperConfig; + import com.google.common.collect.Lists; + import com.google.common.primitives.Doubles; + import com.google.common.primitives.Floats; +@@ -176,6 +177,7 @@ import net.minecraft.world.inventory.InventoryClickType; + import net.minecraft.world.item.crafting.IRecipe; + import net.minecraft.world.level.RayTrace; + import net.minecraft.world.phys.MovingObjectPosition; ++import org.bukkit.Bukkit; // Paper + import org.bukkit.Location; + import org.bukkit.craftbukkit.entity.CraftPlayer; + import org.bukkit.craftbukkit.event.CraftEventFactory; +@@ -234,6 +236,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + private volatile int chatThrottle; + private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(PlayerConnection.class, "chatThrottle"); + 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 j; + private final Int2ShortMap k = new Int2ShortOpenHashMap(); +@@ -382,6 +385,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + // CraftBukkit start + for (int spam; (spam = this.chatThrottle) > 0 && !chatSpamField.compareAndSet(this, spam, spam - 1); ) ; + if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - split to seperate variable ++ if (recipeSpamPackets.get() > 0) recipeSpamPackets.getAndDecrement(); // Paper + /* Use thread-safe field access instead + if (this.chatThrottle > 0) { + --this.chatThrottle; +@@ -2787,6 +2791,14 @@ public class PlayerConnection implements PacketListenerPlayIn { + + @Override + public void a(PacketPlayInAutoRecipe packetplayinautorecipe) { ++ // Paper start ++ if (!Bukkit.isPrimaryThread()) { ++ if (recipeSpamPackets.addAndGet(PaperConfig.autoRecipeIncrement) > PaperConfig.autoRecipeLimit) { ++ minecraftServer.scheduleOnMain(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0]))); // Paper ++ return; ++ } ++ } ++ // Paper end + PlayerConnectionUtils.ensureMainThread(packetplayinautorecipe, this, this.player.getWorldServer()); + this.player.resetIdleTimer(); + if (!this.player.isSpectator() && this.player.activeContainer.windowId == packetplayinautorecipe.b() && this.player.activeContainer.c(this.player) && this.player.activeContainer instanceof ContainerRecipeBook) { diff --git a/patches/server-unmapped/0001/0615-Fix-CraftSound-backwards-compatibility.patch b/patches/server-unmapped/0001/0615-Fix-CraftSound-backwards-compatibility.patch new file mode 100644 index 0000000000..8a131ca40a --- /dev/null +++ b/patches/server-unmapped/0001/0615-Fix-CraftSound-backwards-compatibility.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 17 Dec 2020 15:25:49 -0600 +Subject: [PATCH] Fix CraftSound backwards compatibility + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftSound.java b/src/main/java/org/bukkit/craftbukkit/CraftSound.java +index e839d50c7b1cc3c9a6e463c497489ad580aceabd..84fb7d96bea3b47f2f6c6dc8d0086fee13d07ada 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftSound.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftSound.java +@@ -27,4 +27,10 @@ public class CraftSound { + public static Sound getBukkit(SoundEffect soundEffect) { + return Registry.SOUNDS.get(CraftNamespacedKey.fromMinecraft(IRegistry.SOUND_EVENT.getKey(soundEffect))); + } ++ ++ // Paper start ++ public static String getSound(Sound sound) { ++ return sound.getKey().getKey(); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0616-MC-4-Fix-item-position-desync.patch b/patches/server-unmapped/0001/0616-MC-4-Fix-item-position-desync.patch new file mode 100644 index 0000000000..abc309a820 --- /dev/null +++ b/patches/server-unmapped/0001/0616-MC-4-Fix-item-position-desync.patch @@ -0,0 +1,84 @@ +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/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 652d87fc5d566dba8018c81676329f0e0bca471b..c56e7fb18f9a56c8025eb70a524f028b5942da37 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -474,4 +474,9 @@ public class PaperConfig { + private static void trackPluginScoreboards() { + trackPluginScoreboards = getBoolean("settings.track-plugin-scoreboards", false); + } ++ ++ public static boolean fixEntityPositionDesync = true; ++ private static void fixEntityPositionDesync() { ++ fixEntityPositionDesync = getBoolean("settings.fix-entity-position-desync", fixEntityPositionDesync); ++ } + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutEntity.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutEntity.java +index e80429368afced0299d9f41b97251cd6c64b1759..0eed10a6c4e0c7245f219d19ed1e2e5c94364db9 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutEntity.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutEntity.java +@@ -19,11 +19,11 @@ public class PacketPlayOutEntity implements Packet { + protected boolean i; + + public static long a(double d0) { +- return MathHelper.d(d0 * 4096.0D); ++ return MathHelper.d(d0 * 4096.0D); // Paper - check EntityItem#setPositionRaw on update + } + + public static Vec3D a(long i, long j, long k) { +- return (new Vec3D((double) i, (double) j, (double) k)).a(2.44140625E-4D); ++ return (new Vec3D((double) i, (double) j, (double) k)).a(2.44140625E-4D); // Paper - check EntityItem#setPositionRaw on update + } + + public PacketPlayOutEntity() {} +diff --git a/src/main/java/net/minecraft/util/MathHelper.java b/src/main/java/net/minecraft/util/MathHelper.java +index caa628417bb9c1c65b037e4f3f762b08272c6d09..cc566784c7dd21cc2c44e0f351347f657e57ddcf 100644 +--- a/src/main/java/net/minecraft/util/MathHelper.java ++++ b/src/main/java/net/minecraft/util/MathHelper.java +@@ -9,7 +9,7 @@ import net.minecraft.core.BaseBlockPosition; + public class MathHelper { + + public static final float a = c(2.0F); +- private static final float[] b = (float[]) SystemUtils.a((Object) (new float[65536]), (afloat) -> { ++ private static final float[] b = (float[]) SystemUtils.a((new float[65536]), (afloat) -> { // Paper - decompile error + for (int i = 0; i < afloat.length; ++i) { + afloat[i] = (float) Math.sin((double) i * 3.141592653589793D * 2.0D / 65536.0D); + } +@@ -49,6 +49,7 @@ public class MathHelper { + return d0 < (double) i ? i - 1 : i; + } + ++ public static long floorLong(double d0) { return d(d0); } // Paper - OBFHELPER + public static long d(double d0) { + long i = (long) d0; + +diff --git a/src/main/java/net/minecraft/world/entity/item/EntityItem.java b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +index 11e029f6f97f1dd9c32e311d1a3800f2fa54b91f..575833807ff647f30d7c2b7abcd01701c7dec85b 100644 +--- a/src/main/java/net/minecraft/world/entity/item/EntityItem.java ++++ b/src/main/java/net/minecraft/world/entity/item/EntityItem.java +@@ -550,4 +550,16 @@ public class EntityItem extends Entity { + public Packet P() { + return new PacketPlayOutSpawnEntity(this); + } ++ ++ // Paper start - fix MC-4 ++ public void setPositionRaw(double x, double y, double z) { ++ if (com.destroystokyo.paper.PaperConfig.fixEntityPositionDesync) { ++ // encode/decode from PacketPlayOutEntity ++ x = MathHelper.floorLong(x * 4096.0D) * (1 / 4096.0D); ++ y = MathHelper.floorLong(y * 4096.0D) * (1 / 4096.0D); ++ z = MathHelper.floorLong(z * 4096.0D) * (1 / 4096.0D); ++ } ++ super.setPositionRaw(x, y, z); ++ } ++ // Paper end - fix MC-4 + } diff --git a/patches/server-unmapped/0001/0617-Player-Chunk-Load-Unload-Events.patch b/patches/server-unmapped/0001/0617-Player-Chunk-Load-Unload-Events.patch new file mode 100644 index 0000000000..a81224c1ab --- /dev/null +++ b/patches/server-unmapped/0001/0617-Player-Chunk-Load-Unload-Events.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ysl3000 +Date: Mon, 5 Oct 2020 21:25:16 +0200 +Subject: [PATCH] Player Chunk Load/Unload Events + + +diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index d850721afc33230890353f16c5bc5579c9efb1bf..45c6eb96310146adab802dc3da019f7ee15e0fe5 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -139,6 +139,8 @@ import net.minecraft.world.scores.ScoreboardScore; + import net.minecraft.world.scores.ScoreboardTeam; + import net.minecraft.world.scores.ScoreboardTeamBase; + import net.minecraft.world.scores.criteria.IScoreboardCriteria; ++import io.papermc.paper.event.packet.PlayerChunkLoadEvent; // Paper ++import io.papermc.paper.event.packet.PlayerChunkUnloadEvent; // Paper + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + +@@ -2089,11 +2091,21 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + public void a(ChunkCoordIntPair chunkcoordintpair, Packet packet, Packet packet1) { + this.playerConnection.sendPacket(packet1); + this.playerConnection.sendPacket(packet); ++ // Paper start ++ if(PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0){ ++ new PlayerChunkLoadEvent(this.getBukkitEntity().getWorld().getChunkAt(chunkcoordintpair.longKey), this.getBukkitEntity()).callEvent(); ++ } ++ // Paper end + } + + public void a(ChunkCoordIntPair chunkcoordintpair) { + if (this.isAlive()) { + this.playerConnection.sendPacket(new PacketPlayOutUnloadChunk(chunkcoordintpair.x, chunkcoordintpair.z)); ++ // Paper start ++ if(PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0){ ++ new PlayerChunkUnloadEvent(this.getBukkitEntity().getWorld().getChunkAt(chunkcoordintpair.longKey), this.getBukkitEntity()).callEvent(); ++ } ++ // Paper end + } + + } diff --git a/patches/server-unmapped/0001/0618-Optimize-Dynamic-get-Missing-Keys.patch b/patches/server-unmapped/0001/0618-Optimize-Dynamic-get-Missing-Keys.patch new file mode 100644 index 0000000000..5a4efe035c --- /dev/null +++ b/patches/server-unmapped/0001/0618-Optimize-Dynamic-get-Missing-Keys.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 21 Dec 2020 11:01:42 -0500 +Subject: [PATCH] Optimize Dynamic#get Missing Keys + +get was calling toString() on every NBT object that was ever asked for an optional +key from the object to build a string for the error text. + +When done on large NBT objects, this was using a ton of computation time building the +JSON representation of the NBT object. + +Now we will just skip the value when 99.9999% of the time the text is never even printed. + +diff --git a/src/main/java/com/mojang/serialization/Dynamic.java b/src/main/java/com/mojang/serialization/Dynamic.java +index a75d3db046dc985a03b4b870c91f41de1bd66bad..044facc9de9e8e582d7953d681c0c051578979c3 100644 +--- a/src/main/java/com/mojang/serialization/Dynamic.java ++++ b/src/main/java/com/mojang/serialization/Dynamic.java +@@ -17,6 +17,7 @@ import java.util.stream.Stream; + + @SuppressWarnings("unused") + public class Dynamic extends DynamicLike { ++ private static final boolean DEBUG_MISSING_KEYS = Boolean.getBoolean("Paper.debugDynamicMissingKeys"); // Paper + private final T value; + + public Dynamic(final DynamicOps ops) { +@@ -113,7 +114,7 @@ public class Dynamic extends DynamicLike { + return new OptionalDynamic<>(ops, ops.getMap(value).flatMap(m -> { + final T value = m.get(key); + if (value == null) { +- return DataResult.error("key missing: " + key + " in " + this.value); ++ return DataResult.error(DEBUG_MISSING_KEYS ? "key missing: " + key + " in " + this.value : "key missing: " + key); // Paper + } + return DataResult.success(new Dynamic<>(ops, value)); + })); diff --git a/patches/server-unmapped/0001/0619-Expose-LivingEntity-hurt-direction.patch b/patches/server-unmapped/0001/0619-Expose-LivingEntity-hurt-direction.patch new file mode 100644 index 0000000000..551ec0e06f --- /dev/null +++ b/patches/server-unmapped/0001/0619-Expose-LivingEntity-hurt-direction.patch @@ -0,0 +1,39 @@ +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/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 358bb6244b7b9e785c7dcc3725ee00cfbb917cec..deffd82dfca1d2eea6e5b8db9228015bf35ad0a3 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -163,7 +163,7 @@ public abstract class EntityLiving extends Entity { + public int am; + public int hurtTicks; + public int hurtDuration; +- public float ap; ++ public float ap; public final float getHurtDirection() { return ap; } public final void setHurtDirection(float hurtDirection) { this.ap = hurtDirection; } // Paper - OBFHELPER + public int deathTicks; + public float ar; + public float as; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 5d2ed8a0cf8351df1c8b2946f8a614fe13c34673..e574e2453c7bc848168ff24372d6772bd423b672 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -825,5 +825,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { + getHandle().receive(((CraftItem) item).getHandle(), quantity); + } ++ ++ @Override ++ public float getHurtDirection() { ++ return getHandle().getHurtDirection(); ++ } ++ ++ @Override ++ public void setHurtDirection(float hurtDirection) { ++ getHandle().setHurtDirection(hurtDirection); ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0620-Add-OBSTRUCTED-reason-to-BedEnterResult.patch b/patches/server-unmapped/0001/0620-Add-OBSTRUCTED-reason-to-BedEnterResult.patch new file mode 100644 index 0000000000..2a99fe8f73 --- /dev/null +++ b/patches/server-unmapped/0001/0620-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 2e4dc2fb42b10243ddacbf5af606910a5769ea01..cb577bd576ff099f183b1c9e5d60bd74276c7394 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -264,6 +264,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-unmapped/0001/0621-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch b/patches/server-unmapped/0001/0621-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch new file mode 100644 index 0000000000..e115ba439e --- /dev/null +++ b/patches/server-unmapped/0001/0621-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 27 Dec 2020 11:31:06 +0000 +Subject: [PATCH] Do not crash from invalid ingredient lists in + VillagerAcquireTradeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java +index adcb2070a871223abae0c055f1e88134c38a9e68..76867750e267e8b4d61e59de0a0e8ec9a315d34f 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java +@@ -272,7 +272,11 @@ public abstract class EntityVillagerAbstract extends EntityAgeable implements NP + Bukkit.getPluginManager().callEvent(event); + } + if (!event.isCancelled()) { +- merchantrecipelist.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft()); ++ // Paper start ++ final CraftMerchantRecipe craftMerchantRecipe = CraftMerchantRecipe.fromBukkit(event.getRecipe()); ++ if (craftMerchantRecipe.getIngredients().isEmpty()) return; ++ merchantrecipelist.add(craftMerchantRecipe.toMinecraft()); ++ // Paper end + } + // CraftBukkit end + } diff --git a/patches/server-unmapped/0001/0622-added-PlayerTradeEvent.patch b/patches/server-unmapped/0001/0622-added-PlayerTradeEvent.patch new file mode 100644 index 0000000000..9f38a66f05 --- /dev/null +++ b/patches/server-unmapped/0001/0622-added-PlayerTradeEvent.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 2 Jul 2020 16:12:10 -0700 +Subject: [PATCH] added PlayerTradeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +index 89d24d7532a256434513a45c901946e28db396bd..ff482e3774f580d8ba7028f6c5141888d3bd907a 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java ++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +@@ -93,7 +93,7 @@ import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; + public abstract class EntityInsentient extends EntityLiving { + + private static final DataWatcherObject b = DataWatcher.a(EntityInsentient.class, DataWatcherRegistry.a); +- public int e; ++ public int e;public void setAmbientSoundTime(int time) { this.e = time; } // Paper - OBFHELPER + protected int f; + protected ControllerLook lookController; + protected ControllerMove moveController; +@@ -295,6 +295,7 @@ public abstract class EntityInsentient extends EntityLiving { + this.datawatcher.register(EntityInsentient.b, (byte) 0); + } + ++ public int getAmbientSoundInterval() { return D(); } // Paper - OBFHELPER + public int D() { + return 80; + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java +index 76867750e267e8b4d61e59de0a0e8ec9a315d34f..6182ab078f988ec94f76340deeb48a1a1ab3bca4 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerAbstract.java +@@ -39,6 +39,9 @@ import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe; + import org.bukkit.entity.AbstractVillager; + import org.bukkit.event.entity.VillagerAcquireTradeEvent; + // CraftBukkit end ++// Paper start ++import io.papermc.paper.event.player.PlayerTradeEvent; ++// Paper end + + public abstract class EntityVillagerAbstract extends EntityAgeable implements NPC, IMerchant { + +@@ -135,16 +138,27 @@ public abstract class EntityVillagerAbstract extends EntityAgeable implements NP + + @Override + public void a(MerchantRecipe merchantrecipe) { +- merchantrecipe.increaseUses(); +- this.e = -this.D(); +- this.b(merchantrecipe); ++ // Paper - moved down ++ // Paper start + if (this.tradingPlayer instanceof EntityPlayer) { +- CriterionTriggers.s.a((EntityPlayer) this.tradingPlayer, this, merchantrecipe.getSellingItem()); ++ PlayerTradeEvent event = new PlayerTradeEvent(((EntityPlayer) this.tradingPlayer).getBukkitEntity(), (AbstractVillager) this.getBukkitEntity(), merchantrecipe.asBukkit(), true, true); ++ event.callEvent(); ++ if (!event.isCancelled()) { ++ MerchantRecipe recipe = CraftMerchantRecipe.fromBukkit(event.getTrade()).toMinecraft(); ++ if (event.willIncreaseTradeUses()) recipe.increaseUses(); ++ this.setAmbientSoundTime(-getAmbientSoundInterval()); ++ if (event.isRewardingExp()) this.rewardTradeXp(recipe); ++ CriterionTriggers.s.a((EntityPlayer) this.tradingPlayer, this, recipe.getSellingItem()); ++ } ++ } else { ++ merchantrecipe.increaseUses(); ++ this.setAmbientSoundTime(-getAmbientSoundInterval()); ++ this.rewardTradeXp(merchantrecipe); + } +- ++ // Paper end + } + +- protected abstract void b(MerchantRecipe merchantrecipe); ++ protected abstract void b(MerchantRecipe merchantrecipe); public void rewardTradeXp(MerchantRecipe merchantrecipe) { this.b(merchantrecipe); } // Paper - OBFHELPER + + @Override + public boolean isRegularVillager() { diff --git a/patches/server-unmapped/0001/0623-Implement-TargetHitEvent.patch b/patches/server-unmapped/0001/0623-Implement-TargetHitEvent.patch new file mode 100644 index 0000000000..eebdc5aa97 --- /dev/null +++ b/patches/server-unmapped/0001/0623-Implement-TargetHitEvent.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Wed, 25 Nov 2020 23:20:44 -0800 +Subject: [PATCH] Implement TargetHitEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockTarget.java b/src/main/java/net/minecraft/world/level/block/BlockTarget.java +index c336490815dc17991d3d84d8c6f0fc58571a3e3a..a9316ce8eb3d8a645f4c0e41ac668a90f584c263 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockTarget.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockTarget.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.level.block; + ++import io.papermc.paper.event.block.TargetHitEvent; // Paper - Need to import because 'io' class exists in nms + import java.util.Random; + import net.minecraft.advancements.CriterionTriggers; + import net.minecraft.core.BlockPosition; +@@ -34,6 +35,10 @@ public class BlockTarget extends Block { + @Override + public void a(World world, IBlockData iblockdata, MovingObjectPositionBlock movingobjectpositionblock, IProjectile iprojectile) { + int i = a((GeneratorAccess) world, iblockdata, movingobjectpositionblock, (Entity) iprojectile); ++ // Paper start ++ } ++ private static void awardTargetHitCriteria(IProjectile iprojectile, MovingObjectPositionBlock movingobjectpositionblock, int i) { ++ // Paper end + Entity entity = iprojectile.getShooter(); + + if (entity instanceof EntityPlayer) { +@@ -49,6 +54,20 @@ public class BlockTarget extends Block { + int i = a(movingobjectpositionblock, movingobjectpositionblock.getPos()); + int j = entity instanceof EntityArrow ? 20 : 8; + ++ // Paper start ++ if (entity instanceof IProjectile) { ++ final IProjectile projectile = (IProjectile) entity; ++ final org.bukkit.craftbukkit.block.CraftBlock craftBlock = org.bukkit.craftbukkit.block.CraftBlock.at(generatoraccess, movingobjectpositionblock.getBlockPosition()); ++ final org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(movingobjectpositionblock.getDirection()); ++ final TargetHitEvent targetHitEvent = new TargetHitEvent((org.bukkit.entity.Projectile) projectile.getBukkitEntity(), craftBlock, blockFace, i); ++ if (targetHitEvent.callEvent()) { ++ i = targetHitEvent.getSignalStrength(); ++ awardTargetHitCriteria(projectile, movingobjectpositionblock, i); ++ } else { ++ return i; ++ } ++ } ++ // Paper end + if (!generatoraccess.getBlockTickList().a(movingobjectpositionblock.getBlockPosition(), iblockdata.getBlock())) { + a(generatoraccess, iblockdata, i, movingobjectpositionblock.getBlockPosition(), j); + } diff --git a/patches/server-unmapped/0001/0624-Additional-Block-Material-API-s.patch b/patches/server-unmapped/0001/0624-Additional-Block-Material-API-s.patch new file mode 100644 index 0000000000..ba38cd24b0 --- /dev/null +++ b/patches/server-unmapped/0001/0624-Additional-Block-Material-API-s.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 30 Dec 2020 19:43:01 -0500 +Subject: [PATCH] Additional Block Material API's + +Faster version for isSolid() that utilizes NMS's state for isSolid instead of the slower +process to do this in the Bukkit API + +Adds API for buildable, replaceable, burnable too. + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index fab9e1e5d63c22faceae093dc88769d203d359c6..ed1c92d9f2770f7d0503c6facebc51ddcbdf75cf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -621,6 +621,25 @@ public class CraftBlock implements Block { + return getNMS().getMaterial().isLiquid(); + } + ++ // Paper start ++ @Override ++ public boolean isBuildable() { ++ return getNMS().getMaterial().isBuildable(); ++ } ++ @Override ++ public boolean isBurnable() { ++ return getNMS().getMaterial().isBurnable(); ++ } ++ @Override ++ public boolean isReplaceable() { ++ return getNMS().getMaterial().isReplaceable(); ++ } ++ @Override ++ public boolean isSolid() { ++ return getNMS().getMaterial().isSolid(); ++ } ++ // Paper end ++ + @Override + public PistonMoveReaction getPistonMoveReaction() { + return PistonMoveReaction.getById(getNMS().getPushReaction().ordinal()); diff --git a/patches/server-unmapped/0001/0625-Fix-harming-potion-dupe.patch b/patches/server-unmapped/0001/0625-Fix-harming-potion-dupe.patch new file mode 100644 index 0000000000..0e9ad1d647 --- /dev/null +++ b/patches/server-unmapped/0001/0625-Fix-harming-potion-dupe.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> +Date: Thu, 23 Jul 2020 14:25:07 -0700 +Subject: [PATCH] Fix harming potion dupe + +EntityLiving#applyInstantEffect() immediately kills the player and drops their inventory. +Before this patch, instant effects would be applied before the potion ItemStack is removed and replaced with a glass bottle. This caused the potion ItemStack to be dropped before it was supposed to be removed from the inventory. It also caused the glass bottle to be put into a dead player's inventory. +This patch makes it so that instant effects are applied after the potion ItemStack is removed, and the glass bottle is only put into the player's inventory if the player is not dead. Otherwise, the glass bottle is dropped on the ground. + +diff --git a/src/main/java/net/minecraft/world/item/ItemPotion.java b/src/main/java/net/minecraft/world/item/ItemPotion.java +index b197fe561ab735e80d8bf29ac16eacfaf3fc2d21..ca2f8d522d7f2305f161cb4aa611226355ea5789 100644 +--- a/src/main/java/net/minecraft/world/item/ItemPotion.java ++++ b/src/main/java/net/minecraft/world/item/ItemPotion.java +@@ -15,6 +15,7 @@ import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.item.alchemy.PotionRegistry; + import net.minecraft.world.item.alchemy.PotionUtil; + import net.minecraft.world.item.alchemy.Potions; ++import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.World; + + public class ItemPotion extends Item { +@@ -36,6 +37,7 @@ public class ItemPotion extends Item { + CriterionTriggers.z.a((EntityPlayer) entityhuman, itemstack); + } + ++ List instantLater = new java.util.ArrayList<>(); // Paper - Fix harming potion dupe + if (!world.isClientSide) { + List list = PotionUtil.getEffects(itemstack); + Iterator iterator = list.iterator(); +@@ -44,7 +46,7 @@ public class ItemPotion extends Item { + MobEffect mobeffect = (MobEffect) iterator.next(); + + if (mobeffect.getMobEffect().isInstant()) { +- mobeffect.getMobEffect().applyInstantEffect(entityhuman, entityhuman, entityliving, mobeffect.getAmplifier(), 1.0D); ++ instantLater.add(mobeffect); // Paper - Fix harming potion dupe + } else { + entityliving.addEffect(new MobEffect(mobeffect), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_DRINK); // CraftBukkit + } +@@ -58,7 +60,20 @@ public class ItemPotion extends Item { + } + } + ++ // Paper start - Fix harming potion dupe ++ for (MobEffect mobeffect : instantLater) { ++ mobeffect.getMobEffect().applyInstantEffect(entityhuman, entityhuman, entityliving, mobeffect.getAmplifier(), 1.0D); ++ } ++ // Paper end ++ + if (entityhuman == null || !entityhuman.abilities.canInstantlyBuild) { ++ // Paper start - Fix harming potion dupe ++ if (entityliving.getHealth() <= 0 && !entityliving.world.getGameRules().getBoolean(GameRules.KEEP_INVENTORY)) { ++ entityliving.dropItem(new ItemStack(Items.GLASS_BOTTLE), 0); ++ return ItemStack.NULL_ITEM; ++ } ++ // Paper end ++ + if (itemstack.isEmpty()) { + return new ItemStack(Items.GLASS_BOTTLE); + } diff --git a/patches/server-unmapped/0001/0626-Implement-API-to-get-Material-from-Boats-and-Minecar.patch b/patches/server-unmapped/0001/0626-Implement-API-to-get-Material-from-Boats-and-Minecar.patch new file mode 100644 index 0000000000..02569905c0 --- /dev/null +++ b/patches/server-unmapped/0001/0626-Implement-API-to-get-Material-from-Boats-and-Minecar.patch @@ -0,0 +1,101 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matthew Miller +Date: Thu, 31 Dec 2020 12:48:19 +1000 +Subject: [PATCH] Implement API to get Material from Boats and Minecarts + + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java +index add2aef0192a3b3767c1e477145978b9702c0fb4..2609b83573e0e8532e6c4c36d4f475bf0da6a354 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java +@@ -260,6 +260,7 @@ public class EntityBoat extends Entity { + + } + ++ public final Item getBoatItem() { return this.g(); } // Paper - OBFHELPER + public Item g() { + switch (this.getType()) { + case OAK: +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +index c7a459c0c860724ef1890b8fb9a59a5508b3f6d6..16799dc565c5ca42d1fdb3122594d9dae21c74e0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +@@ -1,8 +1,10 @@ + package org.bukkit.craftbukkit.entity; + + import net.minecraft.world.entity.vehicle.EntityBoat; ++import org.bukkit.Material; // Paper + import org.bukkit.TreeSpecies; + import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; // Paper + import org.bukkit.entity.Boat; + import org.bukkit.entity.EntityType; + +@@ -66,6 +68,13 @@ public class CraftBoat extends CraftVehicle implements Boat { + getHandle().landBoats = workOnLand; + } + ++ // Paper start ++ @Override ++ public Material getBoatMaterial() { ++ return CraftMagicNumbers.getMaterial(getHandle().getBoatItem()); ++ } ++ // Paper end ++ + @Override + public EntityBoat getHandle() { + return (EntityBoat) entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +index 69415f5a838345826fa5cf1d855e057794520f2c..e5ebb80a44da775df6f3d5a9db5cf58295e2e960 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +@@ -1,8 +1,10 @@ + package org.bukkit.craftbukkit.entity; + + import net.minecraft.world.entity.vehicle.EntityMinecartAbstract; ++import net.minecraft.world.item.Items; // Paper + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.IBlockData; ++import org.bukkit.Material; // Paper + import org.bukkit.block.data.BlockData; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.block.data.CraftBlockData; +@@ -68,6 +70,38 @@ public abstract class CraftMinecart extends CraftVehicle implements Minecart { + getHandle().setDerailedVelocityMod(derailed); + } + ++ // Paper start ++ @Override ++ public Material getMinecartMaterial() { ++ net.minecraft.world.item.Item minecartItem; ++ switch (getHandle().getMinecartType()) { ++ case CHEST: ++ minecartItem = Items.CHEST_MINECART; ++ break; ++ case FURNACE: ++ minecartItem = Items.FURNACE_MINECART; ++ break; ++ case TNT: ++ minecartItem = Items.TNT_MINECART; ++ break; ++ case HOPPER: ++ minecartItem = Items.HOPPER_MINECART; ++ break; ++ case COMMAND_BLOCK: ++ minecartItem = Items.COMMAND_BLOCK_MINECART; ++ break; ++ case RIDEABLE: ++ case SPAWNER: ++ minecartItem = Items.MINECART; ++ break; ++ default: ++ throw new IllegalStateException("Unexpected value: " + getHandle().getMinecartType()); ++ } ++ ++ return CraftMagicNumbers.getMaterial(minecartItem); ++ } ++ // Paper end ++ + @Override + public EntityMinecartAbstract getHandle() { + return (EntityMinecartAbstract) entity; diff --git a/patches/server-unmapped/0001/0627-Optimized-tick-ready-check.patch b/patches/server-unmapped/0001/0627-Optimized-tick-ready-check.patch new file mode 100644 index 0000000000..522f5db773 --- /dev/null +++ b/patches/server-unmapped/0001/0627-Optimized-tick-ready-check.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lukas +Date: Sun, 27 Dec 2020 17:19:51 +0100 +Subject: [PATCH] Optimized tick ready check + + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index f40be366ebc5f98b417b677565fa89d3f817f3fb..78dcba08d6d796d5d97c8304bf1f1e7d1e650d5d 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -854,13 +854,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + if (!tileentity.isRemoved() && tileentity.hasWorld()) { + BlockPosition blockposition = tileentity.getPosition(); + +- if (this.getChunkProvider().a(blockposition) && this.getWorldBorder().a(blockposition)) { ++ Chunk chunk; PlayerChunk playerChunk; if ((chunk = tileentity.getCurrentChunk()) != null && (playerChunk = chunk.playerChunk) != null && playerChunk.isTickingReady() && this.getWorldBorder().isInBounds(blockposition)) { // Paper - optimized tick ready check by inlining ChunkProviderServer.a(BlockPosition). Chunk lookup is no longer required and we can use the PlayerChunk directly available through the tile entity + try { + gameprofilerfiller.a(() -> { + return String.valueOf(TileEntityTypes.a(tileentity.getTileType())); + }); + tileentity.tickTimer.startTiming(); // Spigot +- if (tileentity.getTileType().isValidBlock(this.getType(blockposition).getBlock())) { ++ if (tileentity.getTileType().isValidBlock(chunk.getType(blockposition).getBlock())) { // Paper - reuse the chunk from above, do not look it up again + ((ITickable) tileentity).tick(); + } else { + tileentity.w(); +@@ -893,9 +893,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + this.tileEntityListTick.remove(tileTickPosition--); + // Spigot end + //this.tileEntityList.remove(tileentity); // Paper - remove unused list +- if (this.isLoaded(tileentity.getPosition())) { +- this.getChunkAtWorldCoords(tileentity.getPosition()).removeTileEntity(tileentity.getPosition()); ++ // Paper - prevent double chunk lookups ++ Chunk chunk; if ((chunk = this.getChunkIfLoaded(tileentity.getPosition())) != null) { // inlined contents of this.isLoaded(BlockPosition). Reuse the returned chunk instead of looking it up again ++ chunk.removeTileEntity(tileentity.getPosition()); + } ++ // Paper end + } + } + +@@ -914,8 +916,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + } + // CraftBukkit end */ + +- if (this.isLoaded(tileentity1.getPosition())) { +- Chunk chunk = this.getChunkAtWorldCoords(tileentity1.getPosition()); ++ Chunk chunk; if ((chunk = this.getChunkIfLoaded(tileentity1.getPosition())) != null) { // Paper - inlined contents of this.isLoaded(BlockPosition). Reuse the returned chunk instead of looking it up again ++ // Chunk chunk = this.getChunkAtWorldCoords(tileentity1.getPosition()); // Paper - already computed above + IBlockData iblockdata = chunk.getType(tileentity1.getPosition()); + + chunk.setTileEntity(tileentity1.getPosition(), tileentity1); diff --git a/patches/server-unmapped/0001/0628-Cache-burn-durations.patch b/patches/server-unmapped/0001/0628-Cache-burn-durations.patch new file mode 100644 index 0000000000..1c9afd6350 --- /dev/null +++ b/patches/server-unmapped/0001/0628-Cache-burn-durations.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lukas +Date: Sun, 27 Dec 2020 16:47:00 +0100 +Subject: [PATCH] Cache burn durations + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +index e630e8d3e115d2a0177849ad8258a2304b9d3e9d..54316a8079b4331a48cac3c43f3f8c506a4af091 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.level.block.entity; + ++import com.google.common.collect.ImmutableMap; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; +@@ -113,7 +114,15 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + this.c = recipes; + } + ++ private static Map cachedBurnDurations = null; // Paper - cache burn durations ++ ++ public static Map getBurnDurations() { return f(); } // Paper - OBFHELPER + public static Map f() { ++ // Paper start - cache burn durations ++ if(cachedBurnDurations != null) { ++ return cachedBurnDurations; ++ } ++ // Paper end + Map map = Maps.newLinkedHashMap(); + + a(map, (IMaterial) Items.LAVA_BUCKET, 20000); +@@ -176,7 +185,10 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + a(map, (IMaterial) Blocks.FLETCHING_TABLE, 300); + a(map, (IMaterial) Blocks.SMITHING_TABLE, 300); + a(map, (IMaterial) Blocks.COMPOSTER, 300); +- return map; ++ // Paper start - cache burn durations ++ cachedBurnDurations = ImmutableMap.copyOf(map); ++ return cachedBurnDurations; ++ // Paper end + } + + // CraftBukkit start - add fields and methods +@@ -430,7 +442,7 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + } else { + Item item = itemstack.getItem(); + +- return (Integer) f().getOrDefault(item, 0); ++ return getBurnDurations().getOrDefault(item, 0); // Paper - cache burn durations + } + } + +@@ -443,7 +455,7 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + // Paper end + + public static boolean isFuel(ItemStack itemstack) { +- return f().containsKey(itemstack.getItem()); ++ return getBurnDurations().containsKey(itemstack.getItem()); // Paper - cache burn durations + } + + @Override diff --git a/patches/server-unmapped/0001/0629-Allow-disabling-mob-spawner-spawn-egg-transformation.patch b/patches/server-unmapped/0001/0629-Allow-disabling-mob-spawner-spawn-egg-transformation.patch new file mode 100644 index 0000000000..5a7b73fbca --- /dev/null +++ b/patches/server-unmapped/0001/0629-Allow-disabling-mob-spawner-spawn-egg-transformation.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BrodyBeckwith +Date: Fri, 9 Oct 2020 20:30:12 -0400 +Subject: [PATCH] Allow disabling mob spawner spawn egg transformation + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 9ebe8771c2d5e843756868824740ef599ca8455f..a555d040fdc58f7c89ef78e3e6851916fdd8462a 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -720,4 +720,9 @@ public class PaperWorldConfig { + private void fixCuringExploit() { + fixCuringZombieVillagerDiscountExploit = getBoolean("game-mechanics.fix-curing-zombie-villager-discount-exploit", fixCuringZombieVillagerDiscountExploit); + } ++ ++ public boolean disableMobSpawnerSpawnEggTransformation = false; ++ private void disableMobSpawnerSpawnEggTransformation() { ++ disableMobSpawnerSpawnEggTransformation = getBoolean("game-mechanics.disable-mob-spawner-spawn-egg-transformation", disableMobSpawnerSpawnEggTransformation); ++ } + } +diff --git a/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java b/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java +index c170d2141504d80624e3c1a7f78f7968ea8a80ee..4d965e504a40eb52777575df839856c825a0900a 100644 +--- a/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java ++++ b/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java +@@ -60,7 +60,7 @@ public class ItemMonsterEgg extends Item { + EnumDirection enumdirection = itemactioncontext.getClickedFace(); + IBlockData iblockdata = world.getType(blockposition); + +- if (iblockdata.a(Blocks.SPAWNER)) { ++ if (!world.paperConfig.disableMobSpawnerSpawnEggTransformation && iblockdata.a(Blocks.SPAWNER)) { // Paper + TileEntity tileentity = world.getTileEntity(blockposition); + + if (tileentity instanceof TileEntityMobSpawner) { diff --git a/patches/server-unmapped/0001/0630-Implement-PlayerFlowerPotManipulateEvent.patch b/patches/server-unmapped/0001/0630-Implement-PlayerFlowerPotManipulateEvent.patch new file mode 100644 index 0000000000..43503c58f0 --- /dev/null +++ b/patches/server-unmapped/0001/0630-Implement-PlayerFlowerPotManipulateEvent.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MisterVector +Date: Tue, 13 Aug 2019 19:45:06 -0700 +Subject: [PATCH] Implement PlayerFlowerPotManipulateEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockFlowerPot.java b/src/main/java/net/minecraft/world/level/block/BlockFlowerPot.java +index a61d1ffeebfd00a5fcd5faf95200b0640da8ea82..18fefad056d92a6fa498ab3764cd391446c26b60 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockFlowerPot.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockFlowerPot.java +@@ -21,6 +21,8 @@ import net.minecraft.world.phys.MovingObjectPositionBlock; + import net.minecraft.world.phys.shapes.VoxelShape; + import net.minecraft.world.phys.shapes.VoxelShapeCollision; + ++import io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent; // Paper ++ + public class BlockFlowerPot extends Block { + + private static final Map b = Maps.newHashMap(); +@@ -52,6 +54,27 @@ public class BlockFlowerPot extends Block { + boolean flag1 = this.c == Blocks.AIR; + + if (flag != flag1) { ++ // Paper start ++ org.bukkit.entity.Player player = (org.bukkit.entity.Player) entityhuman.getBukkitEntity(); ++ boolean placing = flag1; ++ org.bukkit.block.Block bukkitblock = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition); ++ org.bukkit.inventory.ItemStack bukkititemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack); ++ org.bukkit.Material mat = org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(c); ++ org.bukkit.inventory.ItemStack bukkititemstack1 = new org.bukkit.inventory.ItemStack(mat, 1); ++ org.bukkit.inventory.ItemStack whichitem = placing ? bukkititemstack : bukkititemstack1; ++ ++ PlayerFlowerPotManipulateEvent event = new PlayerFlowerPotManipulateEvent(player, bukkitblock, whichitem, placing); ++ player.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ // Update client ++ player.sendBlockChange(bukkitblock.getLocation(), bukkitblock.getBlockData()); ++ player.updateInventory(); ++ ++ return EnumInteractionResult.PASS; ++ } ++ // Paper end ++ + if (flag1) { + world.setTypeAndData(blockposition, block.getBlockData(), 3); + entityhuman.a(StatisticList.POT_FLOWER); diff --git a/patches/server-unmapped/0001/0631-Fix-interact-event-not-being-called-in-adventure.patch b/patches/server-unmapped/0001/0631-Fix-interact-event-not-being-called-in-adventure.patch new file mode 100644 index 0000000000..e0ef42968c --- /dev/null +++ b/patches/server-unmapped/0001/0631-Fix-interact-event-not-being-called-in-adventure.patch @@ -0,0 +1,29 @@ +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 in adventure + +Call PlayerInteractEvent when left-clicking on a block in adventure mode + +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 79c0ef65f3a1d28de73afb3f1891cfc1a0a3dd90..358d1095b219fce6b308ec0362f22db7cfc85251 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1702,7 +1702,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + IChatMutableComponent ichatmutablecomponent = (new ChatMessage("build.tooHigh", new Object[]{this.minecraftServer.getMaxBuildHeight()})).a(EnumChatFormat.RED); + + this.player.playerConnection.sendPacket(new PacketPlayOutChat(ichatmutablecomponent, ChatMessageType.GAME_INFO, SystemUtils.b)); +- } else if (enuminteractionresult.b()) { ++ } else if (enuminteractionresult.b() && !this.player.playerInteractManager.interactResult) { + this.player.swingHand(enumhand, true); + } + } +@@ -2203,7 +2203,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + Vec3D vec3d1 = vec3d.add((double) f7 * d3, (double) f6 * d3, (double) f8 * d3); + MovingObjectPosition movingobjectposition = this.player.world.rayTrace(new RayTrace(vec3d, vec3d1, RayTrace.BlockCollisionOption.OUTLINE, RayTrace.FluidCollisionOption.NONE, player)); + +- if (movingobjectposition == null || movingobjectposition.getType() != MovingObjectPosition.EnumMovingObjectType.BLOCK) { ++ if (movingobjectposition == null || movingobjectposition.getType() != MovingObjectPosition.EnumMovingObjectType.BLOCK || this.player.playerInteractManager.getGameMode() == EnumGamemode.ADVENTURE) { // Paper - call PlayerInteractEvent when left-clicking on a block in adventure mode + CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.inventory.getItemInHand(), EnumHand.MAIN_HAND); + } + diff --git a/patches/server-unmapped/0001/0632-Zombie-API-breaking-doors.patch b/patches/server-unmapped/0001/0632-Zombie-API-breaking-doors.patch new file mode 100644 index 0000000000..da61412550 --- /dev/null +++ b/patches/server-unmapped/0001/0632-Zombie-API-breaking-doors.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 18 Nov 2020 11:32:46 -0800 +Subject: [PATCH] Zombie API - breaking doors + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +index 87acbdee03edf8bc35f4b3bcb9b82855ed4a3c33..5b0c79d752d616e5824393968136f3844ce52c22 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +@@ -140,10 +140,12 @@ public class EntityZombie extends EntityMonster { + return (Boolean) this.getDataWatcher().get(EntityZombie.DROWN_CONVERTING); + } + ++ public boolean canBreakDoors() { return this.eU(); } // Paper - OBFHELPER + public boolean eU() { + return this.bs; + } + ++ public void setCanBreakDoors(boolean canBreakDoors) { this.u(canBreakDoors); } // Paper - OBFHELPER + public void u(boolean flag) { + if (this.eK() && PathfinderGoalUtil.a(this)) { + if (this.bs != flag) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +index 42d98d798bb8fe2d3c7cc2bfcf2ec38d97d99bd2..e1589ef97222f5e29a3628db354d4406a443a613 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +@@ -129,6 +129,16 @@ public class CraftZombie extends CraftMonster implements Zombie { + public void setShouldBurnInDay(boolean shouldBurnInDay) { + getHandle().setShouldBurnInDay(shouldBurnInDay); + } ++ ++ @Override ++ public boolean canBreakDoors() { ++ return getHandle().canBreakDoors(); ++ } ++ ++ @Override ++ public void setCanBreakDoors(boolean canBreakDoors) { ++ getHandle().setCanBreakDoors(canBreakDoors); ++ } + // Paper end + + @Override diff --git a/patches/server-unmapped/0001/0633-Fix-nerfed-slime-when-splitting.patch b/patches/server-unmapped/0001/0633-Fix-nerfed-slime-when-splitting.patch new file mode 100644 index 0000000000..875960eb0b --- /dev/null +++ b/patches/server-unmapped/0001/0633-Fix-nerfed-slime-when-splitting.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 24 Aug 2020 08:39:06 -0700 +Subject: [PATCH] Fix nerfed slime when splitting + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java b/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java +index 40e39e382092b1a8f831da0cea1557a781c98600..0af0b232ff1b6f1d58cf3fb543d32bd108be0af7 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntitySlime.java +@@ -246,6 +246,7 @@ public class EntitySlime extends EntityInsentient implements IMonster { + entityslime.setPersistent(); + } + ++ entityslime.aware = this.aware; // Paper + entityslime.setCustomName(ichatbasecomponent); + entityslime.setNoAI(flag); + entityslime.setInvulnerable(this.isInvulnerable()); diff --git a/patches/server-unmapped/0001/0634-Add-EntityLoadCrossbowEvent.patch b/patches/server-unmapped/0001/0634-Add-EntityLoadCrossbowEvent.patch new file mode 100644 index 0000000000..5e9ae2150b --- /dev/null +++ b/patches/server-unmapped/0001/0634-Add-EntityLoadCrossbowEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Wed, 7 Oct 2020 12:04:01 -0400 +Subject: [PATCH] Add EntityLoadCrossbowEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ItemCrossbow.java b/src/main/java/net/minecraft/world/item/ItemCrossbow.java +index ec6c0836f02e7ac5b72fd224a3022a844dce55cb..4e1e819c8535e8a6b9aa5f76afe568ea171b939f 100644 +--- a/src/main/java/net/minecraft/world/item/ItemCrossbow.java ++++ b/src/main/java/net/minecraft/world/item/ItemCrossbow.java +@@ -3,6 +3,8 @@ package net.minecraft.world.item; + import com.google.common.collect.Lists; + import com.mojang.math.Quaternion; + import com.mojang.math.Vector3fa; ++import org.bukkit.inventory.EquipmentSlot; // Paper ++import io.papermc.paper.event.entity.EntityLoadCrossbowEvent; // Paper - EntityLoadCrossbowEvent namespace conflicts + import java.util.List; + import java.util.Random; + import java.util.function.Predicate; +@@ -73,7 +75,11 @@ public class ItemCrossbow extends ItemProjectileWeapon implements ItemVanishable + int j = this.e_(itemstack) - i; + float f = a(j, itemstack); + +- if (f >= 1.0F && !d(itemstack) && a(entityliving, itemstack)) { ++ // Paper start - EntityLoadCrossbowEvent ++ if (f >= 1.0F && !d(itemstack) /*&& a(entityliving, itemstack)*/) { ++ final EntityLoadCrossbowEvent event = new EntityLoadCrossbowEvent(entityliving.getBukkitLivingEntity(), itemstack.asBukkitMirror(), entityliving.getRaisedHand() == EnumHand.MAIN_HAND ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND); ++ if (!event.callEvent() || !attemptProjectileLoad(entityliving, itemstack, event.shouldConsumeItem())) return; ++ // Paper end + a(itemstack, true); + SoundCategory soundcategory = entityliving instanceof EntityHuman ? SoundCategory.PLAYERS : SoundCategory.HOSTILE; + +@@ -82,10 +88,13 @@ public class ItemCrossbow extends ItemProjectileWeapon implements ItemVanishable + + } + +- private static boolean a(EntityLiving entityliving, ItemStack itemstack) { ++ private static boolean attemptProjectileLoad(EntityLiving ent, ItemStack bow) { return a(ent, bow); } // Paper - EntityLoadCrossbowEvent - OBFHELPER ++ private static boolean attemptProjectileLoad(EntityLiving ent, ItemStack bow, boolean consume) { return a(ent, bow, consume); } // Paper - EntityLoadCrossbowEvent - OBFHELPER ++ private static boolean a(EntityLiving entityliving, ItemStack itemstack) { return a(entityliving, itemstack, true); };// Paper - add consume ++ private static boolean a(EntityLiving entityliving, ItemStack itemstack, boolean consume) { // Paper - add consume + int i = EnchantmentManager.getEnchantmentLevel(Enchantments.MULTISHOT, itemstack); + int j = i == 0 ? 1 : 3; +- boolean flag = entityliving instanceof EntityHuman && ((EntityHuman) entityliving).abilities.canInstantlyBuild; ++ boolean flag = !consume || entityliving instanceof EntityHuman && ((EntityHuman) entityliving).abilities.canInstantlyBuild; // Paper - add consme + ItemStack itemstack1 = entityliving.f(itemstack); + ItemStack itemstack2 = itemstack1.cloneItemStack(); + diff --git a/patches/server-unmapped/0001/0635-Guardian-beam-workaround.patch b/patches/server-unmapped/0001/0635-Guardian-beam-workaround.patch new file mode 100644 index 0000000000..4439204159 --- /dev/null +++ b/patches/server-unmapped/0001/0635-Guardian-beam-workaround.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gabscap +Date: Sat, 19 Mar 2016 22:25:11 +0100 +Subject: [PATCH] Guardian beam workaround + +This patch is a workaround for MC-165595 + +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutUpdateTime.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutUpdateTime.java +index a69e60a8934493f6786ce3d425f6dccb6e4befdd..3086ee023685781d94e2fb99fc8dff5264f01165 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutUpdateTime.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutUpdateTime.java +@@ -6,7 +6,7 @@ import net.minecraft.network.protocol.Packet; + + public class PacketPlayOutUpdateTime implements Packet { + +- private long a; ++ private long a; private final void setWorldAge(final long age) { this.a = age; } private final long getWorldAge() { return this.a; } // Paper - OBFHELPER + private long b; + + public PacketPlayOutUpdateTime() {} +@@ -21,6 +21,9 @@ public class PacketPlayOutUpdateTime implements Packet { + } + } + ++ // Paper start ++ this.setWorldAge(this.getWorldAge() % 192000); ++ // Paper end + } + + @Override diff --git a/patches/server-unmapped/0001/0636-Added-WorldGameRuleChangeEvent.patch b/patches/server-unmapped/0001/0636-Added-WorldGameRuleChangeEvent.patch new file mode 100644 index 0000000000..e963bb0ecb --- /dev/null +++ b/patches/server-unmapped/0001/0636-Added-WorldGameRuleChangeEvent.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 20 Dec 2020 16:41:44 -0800 +Subject: [PATCH] Added WorldGameRuleChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/CommandGamerule.java b/src/main/java/net/minecraft/server/commands/CommandGamerule.java +index 5b5c5d1299c267f620f5c59873b261a7b7e382a9..feebe4812ff7aec17a50cb5f2789fe88e10a5032 100644 +--- a/src/main/java/net/minecraft/server/commands/CommandGamerule.java ++++ b/src/main/java/net/minecraft/server/commands/CommandGamerule.java +@@ -31,7 +31,7 @@ public class CommandGamerule { + CommandListenerWrapper commandlistenerwrapper = (CommandListenerWrapper) commandcontext.getSource(); + T t0 = commandlistenerwrapper.getWorld().getGameRules().get(gamerules_gamerulekey); // CraftBukkit + +- t0.b(commandcontext, "value"); ++ t0.setValue(commandcontext, "value", gamerules_gamerulekey); // Paper + commandlistenerwrapper.sendMessage(new ChatMessage("commands.gamerule.set", new Object[]{gamerules_gamerulekey.a(), t0.toString()}), true); + return t0.getIntValue(); + } +diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java +index 276c28170b2a177dab6b2a0d5425044cd9f8df22..3783f3a83e3e70d77cf0fa1021f62a89c5950af5 100644 +--- a/src/main/java/net/minecraft/world/level/GameRules.java ++++ b/src/main/java/net/minecraft/world/level/GameRules.java +@@ -25,6 +25,7 @@ import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.EntityPlayer; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import io.papermc.paper.event.world.WorldGameRuleChangeEvent; // Paper + + public class GameRules { + +@@ -177,8 +178,11 @@ public class GameRules { + } + + @Override +- protected void a(CommandContext commandcontext, String s) { +- this.b = BoolArgumentType.getBool(commandcontext, s); ++ protected void a(CommandContext commandcontext, String s, GameRules.GameRuleKey gameRuleKey) { // Paper start ++ WorldGameRuleChangeEvent event = new WorldGameRuleChangeEvent(commandcontext.getSource().getBukkitWorld(), commandcontext.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(commandcontext, s))); ++ if (!event.callEvent()) return; ++ this.b = Boolean.parseBoolean(event.getValue()); ++ // Paper end + } + + public boolean a() { +@@ -237,8 +241,11 @@ public class GameRules { + } + + @Override +- protected void a(CommandContext commandcontext, String s) { +- this.b = IntegerArgumentType.getInteger(commandcontext, s); ++ protected void a(CommandContext commandcontext, String s, GameRules.GameRuleKey gameRuleKey) { // Paper start ++ WorldGameRuleChangeEvent event = new WorldGameRuleChangeEvent(commandcontext.getSource().getBukkitWorld(), commandcontext.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(commandcontext, s))); ++ if (!event.callEvent()) return; ++ this.b = Integer.parseInt(event.getValue()); ++ // Paper end + } + + public int a() { +@@ -291,10 +298,12 @@ public class GameRules { + this.a = gamerules_gameruledefinition; + } + +- protected abstract void a(CommandContext commandcontext, String s); ++ protected void updateValue(CommandContext commandcontext, String s, GameRules.GameRuleKey gameRuleKey) { this.a(commandcontext, s, gameRuleKey); } // Paper - OBFHELPER ++ protected abstract void a(CommandContext commandcontext, String s, GameRules.GameRuleKey gameRuleKey); // Paper + +- public void b(CommandContext commandcontext, String s) { +- this.a(commandcontext, s); ++ public void setValue(CommandContext commandcontext, String s, GameRules.GameRuleKey gameRuleKey) { this.b(commandcontext, s, gameRuleKey); } // Paper - OBFHELPER ++ public void b(CommandContext commandcontext, String s, GameRules.GameRuleKey gameRuleKey) { // Paper ++ this.updateValue(commandcontext, s, gameRuleKey); // Paper + this.onChange(((CommandListenerWrapper) commandcontext.getSource()).getServer()); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index ac0fc981c1d6a9e75a062363535630ebf4937840..ef353e21f7e04162d886e70012f845334962459b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2363,8 +2363,13 @@ public class CraftWorld implements World { + + if (!isGameRule(rule)) return false; + ++ // Paper start ++ 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 + GameRules.GameRuleValue handle = getHandle().getGameRules().get(getGameRulesNMS().get(rule)); +- handle.setValue(value); ++ handle.setValue(event.getValue().toString()); // Paper + handle.onChange(getHandle().getMinecraftServer()); + return true; + } +@@ -2399,8 +2404,12 @@ public class CraftWorld implements World { + + if (!isGameRule(rule.getName())) return false; + ++ // Paper start ++ 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 + GameRules.GameRuleValue handle = getHandle().getGameRules().get(getGameRulesNMS().get(rule.getName())); +- handle.setValue(newValue.toString()); ++ handle.setValue(event.getValue().toString()); // Paper + handle.onChange(getHandle().getMinecraftServer()); + return true; + } diff --git a/patches/server-unmapped/0001/0637-Added-ServerResourcesReloadedEvent.patch b/patches/server-unmapped/0001/0637-Added-ServerResourcesReloadedEvent.patch new file mode 100644 index 0000000000..49af0db962 --- /dev/null +++ b/patches/server-unmapped/0001/0637-Added-ServerResourcesReloadedEvent.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Dec 2020 20:04:01 -0800 +Subject: [PATCH] Added ServerResourcesReloadedEvent + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 07021c760b0d734b91240ca96c6480be469a1005..0ff63eede271b555e9de9b61dc76045d450cd990 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2,9 +2,6 @@ package net.minecraft.server; + + import com.google.common.base.Splitter; + import com.google.common.collect.ImmutableList; +-import co.aikar.timings.Timings; +-import com.destroystokyo.paper.event.server.PaperServerListPingEvent; +-import com.google.common.base.Stopwatch; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -182,6 +179,7 @@ import org.bukkit.event.server.ServerLoadEvent; + import co.aikar.timings.MinecraftTimings; // Paper + import org.spigotmc.SlackActivityAccountant; // Spigot + import io.papermc.paper.util.PaperJvmChecker; // Paper ++import io.papermc.paper.event.server.ServerResourcesReloadedEvent; // Paper + + public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant implements IMojangStatistics, ICommandListener, AutoCloseable { + +@@ -1934,7 +1932,13 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant a(Collection collection) { ++ return this.reloadServerResources(collection, ServerResourcesReloadedEvent.Cause.PLUGIN); ++ } ++ public CompletableFuture reloadServerResources(Collection collection, ServerResourcesReloadedEvent.Cause cause) { ++ // Paper end + CompletableFuture completablefuture = CompletableFuture.supplyAsync(() -> { + Stream stream = collection.stream(); // CraftBukkit - decompile error + ResourcePackRepository resourcepackrepository = this.resourcePackRepository; +@@ -1950,6 +1954,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant collection, CommandListenerWrapper commandlistenerwrapper) { +- commandlistenerwrapper.getServer().a(collection).exceptionally((throwable) -> { ++ commandlistenerwrapper.getServer().reloadServerResources(collection, ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { // Paper + CommandReload.LOGGER.warn("Failed to execute reload", throwable); + commandlistenerwrapper.sendFailureMessage(new ChatMessage("commands.reload.failure")); + return null; +@@ -48,7 +49,7 @@ public class CommandReload { + SaveData savedata = minecraftserver.getSaveData(); + Collection collection = resourcepackrepository.d(); + Collection collection1 = a(resourcepackrepository, savedata, collection); +- minecraftserver.a(collection1); ++ minecraftserver.reloadServerResources(collection1, ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper + } + // CraftBukkit end + diff --git a/patches/server-unmapped/0001/0638-Added-world-settings-for-mobs-picking-up-loot.patch b/patches/server-unmapped/0001/0638-Added-world-settings-for-mobs-picking-up-loot.patch new file mode 100644 index 0000000000..2685a66e73 --- /dev/null +++ b/patches/server-unmapped/0001/0638-Added-world-settings-for-mobs-picking-up-loot.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 28 Nov 2020 18:43:52 -0800 +Subject: [PATCH] Added world settings for mobs picking up loot + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index a555d040fdc58f7c89ef78e3e6851916fdd8462a..c42e5d9f9c9f67988383c4c25123d8a629ede9e3 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -361,6 +361,14 @@ public class PaperWorldConfig { + log("Creeper lingering effect: " + disableCreeperLingeringEffect); + } + ++ public boolean zombiesAlwaysCanPickUpLoot; ++ public boolean skeletonsAlwaysCanPickUpLoot; ++ private void setMobsAlwaysCanPickUpLoot() { ++ zombiesAlwaysCanPickUpLoot = getBoolean("mobs-can-always-pick-up-loot.zombies", false); ++ skeletonsAlwaysCanPickUpLoot = getBoolean("mobs-can-always-pick-up-loot.skeletons", false); ++ log("Zombies can always pick up loot: " + zombiesAlwaysCanPickUpLoot + ". Skeletons can always pick up loot: " + skeletonsAlwaysCanPickUpLoot + "."); ++ } ++ + public int expMergeMaxValue; + private void expMergeMaxValue() { + expMergeMaxValue = getInt("experience-merge-max-value", -1); +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntitySkeletonAbstract.java b/src/main/java/net/minecraft/world/entity/monster/EntitySkeletonAbstract.java +index 4dca5ea9127c15b2739483b2ad74a5296a6b96ad..06d50b22ede102556fdb3e2a6f1424f7ff13f120 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntitySkeletonAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntitySkeletonAbstract.java +@@ -149,7 +149,7 @@ public abstract class EntitySkeletonAbstract extends EntityMonster implements IR + this.a(difficultydamagescaler); + this.b(difficultydamagescaler); + this.eL(); +- this.setCanPickupLoot(this.random.nextFloat() < 0.55F * difficultydamagescaler.d()); ++ this.setCanPickupLoot(this.world.paperConfig.skeletonsAlwaysCanPickUpLoot || this.random.nextFloat() < 0.55F * difficultydamagescaler.d()); // Paper + if (this.getEquipment(EnumItemSlot.HEAD).isEmpty()) { + LocalDate localdate = LocalDate.now(); + int i = localdate.get(ChronoField.DAY_OF_MONTH); +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +index 5b0c79d752d616e5824393968136f3844ce52c22..fb98609a38d665659076b8949b59eaf084408a17 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +@@ -497,7 +497,7 @@ public class EntityZombie extends EntityMonster { + Object object = super.prepare(worldaccess, difficultydamagescaler, enummobspawn, groupdataentity, nbttagcompound); + float f = difficultydamagescaler.d(); + +- this.setCanPickupLoot(this.random.nextFloat() < 0.55F * f); ++ this.setCanPickupLoot(this.world.paperConfig.zombiesAlwaysCanPickUpLoot || this.random.nextFloat() < 0.55F * f); // Paper + if (object == null) { + object = new EntityZombie.GroupDataZombie(a(worldaccess.getRandom()), true); + } diff --git a/patches/server-unmapped/0001/0639-Implemented-BlockFailedDispenseEvent.patch b/patches/server-unmapped/0001/0639-Implemented-BlockFailedDispenseEvent.patch new file mode 100644 index 0000000000..1ed974fb01 --- /dev/null +++ b/patches/server-unmapped/0001/0639-Implemented-BlockFailedDispenseEvent.patch @@ -0,0 +1,63 @@ +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] Implemented BlockFailedDispenseEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockDispenser.java b/src/main/java/net/minecraft/world/level/block/BlockDispenser.java +index 0ab6186ba3cfd7f7115c71b3982f46c5d2c921c0..966051ab3e720e5b3f0fb9ab852c8908c5f23f3b 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockDispenser.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockDispenser.java +@@ -81,6 +81,7 @@ public class BlockDispenser extends BlockTileEntity { + int i = tileentitydispenser.h(); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(worldserver, blockposition)) // Paper - BlockFailedDispenseEvent is called here + worldserver.triggerEffect(1001, blockposition, 0); + } else { + ItemStack itemstack = tileentitydispenser.getItem(i); +diff --git a/src/main/java/net/minecraft/world/level/block/BlockDropper.java b/src/main/java/net/minecraft/world/level/block/BlockDropper.java +index ccab4714bf5a6be8afd92430874fd6f881d4f92f..223cc0ba06cf4b007049880daad881e55ac4e448 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockDropper.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockDropper.java +@@ -47,6 +47,7 @@ public class BlockDropper extends BlockDispenser { + int i = tileentitydispenser.h(); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(worldserver, blockposition)) // Paper - BlockFailedDispenseEvent is called here + worldserver.triggerEffect(1001, blockposition, 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 cb577bd576ff099f183b1c9e5d60bd74276c7394..93157e38f40af84341b8bb20598cf07118e723bc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -4,6 +4,7 @@ import com.google.common.base.Function; + import com.google.common.base.Functions; + import com.google.common.collect.Lists; + import com.mojang.datafixers.util.Either; ++import io.papermc.paper.event.block.BlockFailedDispenseEvent; + import java.net.InetAddress; + import java.util.ArrayList; + import java.util.Collections; +@@ -119,7 +120,6 @@ import org.bukkit.entity.ThrownPotion; + import org.bukkit.entity.Vehicle; + import org.bukkit.entity.Villager; + import org.bukkit.entity.Villager.Profession; +-import org.bukkit.entity.ExperienceOrb; // Paper + import org.bukkit.event.Cancellable; + import org.bukkit.event.Event; + import org.bukkit.event.Event.Result; +@@ -1762,4 +1762,12 @@ public class CraftEventFactory { + + return event; + } ++ ++ // Paper start ++ public static boolean handleBlockFailedDispenseEvent(WorldServer worldserver, BlockPosition blockposition) { ++ org.bukkit.block.Block block = worldserver.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ BlockFailedDispenseEvent event = new BlockFailedDispenseEvent(block); ++ return event.callEvent(); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0640-Added-PlayerLecternPageChangeEvent.patch b/patches/server-unmapped/0001/0640-Added-PlayerLecternPageChangeEvent.patch new file mode 100644 index 0000000000..8e2dd9cf60 --- /dev/null +++ b/patches/server-unmapped/0001/0640-Added-PlayerLecternPageChangeEvent.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 23 Nov 2020 12:58:51 -0800 +Subject: [PATCH] Added PlayerLecternPageChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/inventory/Container.java b/src/main/java/net/minecraft/world/inventory/Container.java +index e7c29d194d5c3e3b1b79228758f7a3d8aa060fbd..9c6330da4e026a7753698b5d103c009730154c3e 100644 +--- a/src/main/java/net/minecraft/world/inventory/Container.java ++++ b/src/main/java/net/minecraft/world/inventory/Container.java +@@ -562,6 +562,7 @@ public abstract class Container { + this.getSlot(i).set(itemstack); + } + ++ public void setData(int index, int value) { this.a(index, value); } // Paper - OBFHELPER + public void a(int i, int j) { + ((ContainerProperty) this.d.get(i)).set(j); + } +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerLectern.java b/src/main/java/net/minecraft/world/inventory/ContainerLectern.java +index 910c3eab0675b84694317427d7317caba89d74c3..10c829efd9ecb7dfa29e92586f99e40a8984f840 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerLectern.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerLectern.java +@@ -13,6 +13,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryView; + import org.bukkit.entity.Player; + import org.bukkit.event.player.PlayerTakeLecternBookEvent; + // CraftBukkit end ++import io.papermc.paper.event.player.PlayerLecternPageChangeEvent; // Paper + + public class ContainerLectern extends Container { + +@@ -60,6 +61,7 @@ public class ContainerLectern extends Container { + @Override + public boolean a(EntityHuman entityhuman, int i) { + int j; ++ PlayerLecternPageChangeEvent playerLecternPageChangeEvent; CraftInventoryLectern bukkitView; // Paper + + if (i >= 100) { + j = i - 100; +@@ -69,11 +71,25 @@ public class ContainerLectern extends Container { + switch (i) { + case 1: + j = this.containerProperties.getProperty(0); +- this.a(0, j - 1); ++ // Paper start ++ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory(); ++ playerLecternPageChangeEvent = new PlayerLecternPageChangeEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), PlayerLecternPageChangeEvent.PageChangeDirection.LEFT, j, j - 1); ++ if (!playerLecternPageChangeEvent.callEvent()) { ++ return false; ++ } ++ this.setData(0, playerLecternPageChangeEvent.getNewPage()); ++ // Paper end + return true; + case 2: + j = this.containerProperties.getProperty(0); +- this.a(0, j + 1); ++ // Paper start ++ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory(); ++ playerLecternPageChangeEvent = new PlayerLecternPageChangeEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), PlayerLecternPageChangeEvent.PageChangeDirection.RIGHT, j, j + 1); ++ if (!playerLecternPageChangeEvent.callEvent()) { ++ return false; ++ } ++ this.setData(0, playerLecternPageChangeEvent.getNewPage()); ++ // Paper end + return true; + case 3: + if (!entityhuman.eK()) { diff --git a/patches/server-unmapped/0001/0641-Fire-event-on-GS4-query.patch b/patches/server-unmapped/0001/0641-Fire-event-on-GS4-query.patch new file mode 100644 index 0000000000..24dd766968 --- /dev/null +++ b/patches/server-unmapped/0001/0641-Fire-event-on-GS4-query.patch @@ -0,0 +1,204 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Sun, 17 Mar 2019 21:46:56 +0200 +Subject: [PATCH] Fire event on GS4 query + + +diff --git a/src/main/java/net/minecraft/server/rcon/RemoteStatusReply.java b/src/main/java/net/minecraft/server/rcon/RemoteStatusReply.java +index 57ff3db0a0199ef03045b880e598407886b0306b..1b6761f75d97c49b1bf1ce3db631b7250c9489bd 100644 +--- a/src/main/java/net/minecraft/server/rcon/RemoteStatusReply.java ++++ b/src/main/java/net/minecraft/server/rcon/RemoteStatusReply.java +@@ -18,15 +18,27 @@ public class RemoteStatusReply { + this.b.write(abyte, 0, abyte.length); + } + ++ public void writeString(String string) throws IOException { this.a(string); } // Paper - OBFHELPER + public void a(String s) throws IOException { + this.b.writeBytes(s); + this.b.write(0); + } ++ // Paper start - unchecked exception variant to use in Stream API ++ public void writeStringUnchecked(String string) { ++ try { ++ writeString(string); ++ } catch (IOException e) { ++ com.destroystokyo.paper.util.SneakyThrow.sneaky(e); ++ } ++ } ++ // Paper end + ++ public void writeInt(int i) throws IOException { this.a(i); } // Paper - OBFHELPER + public void a(int i) throws IOException { + this.b.write(i); + } + ++ public void writeShort(short i) throws IOException { this.a(i); } // Paper - OBFHELPER + public void a(short short0) throws IOException { + this.b.writeShort(Short.reverseBytes(short0)); + } +diff --git a/src/main/java/net/minecraft/server/rcon/thread/RemoteStatusListener.java b/src/main/java/net/minecraft/server/rcon/thread/RemoteStatusListener.java +index 55b379af2e0c8c3513a76a346d381cd3dbcabe40..59769f7ea808b952cd02192c7121c248756998e9 100644 +--- a/src/main/java/net/minecraft/server/rcon/thread/RemoteStatusListener.java ++++ b/src/main/java/net/minecraft/server/rcon/thread/RemoteStatusListener.java +@@ -16,6 +16,7 @@ import java.util.Random; + import javax.annotation.Nullable; + import net.minecraft.SystemUtils; + import net.minecraft.server.IMinecraftServer; ++import net.minecraft.server.dedicated.DedicatedServer; + import net.minecraft.server.rcon.RemoteStatusReply; + import net.minecraft.server.rcon.StatusChallengeUtils; + import org.apache.logging.log4j.LogManager; +@@ -26,18 +27,18 @@ public class RemoteStatusListener extends RemoteConnectionThread { + private static final Logger LOGGER = LogManager.getLogger(); + private long e; + private final int f; +- private final int g; +- private final int h; +- private final String i; +- private final String j; ++ private final int g; private final int getServerPort() { return this.g; } // Paper - OBFHELPER ++ private final int h; private final int getMaxPlayers() { return this.h; } // Paper - OBFHELPER ++ private final String i; private final String getMotd() { return this.i; } // Paper - OBFHELPER ++ private final String j; private final String getWorldName() { return this.j; } // Paper - OBFHELPER + private DatagramSocket k; + private final byte[] l = new byte[1460]; +- private String m; ++ private String m; public final String getServerHost() { return this.m; } // Paper - OBFHELPER + private String n; + private final Map o; +- private final RemoteStatusReply p; ++ private final RemoteStatusReply p; private final RemoteStatusReply getCachedFullResponse() { return this.p; } // Paper - OBFHELPER + private long q; +- private final IMinecraftServer r; ++ private final IMinecraftServer r; private final IMinecraftServer getServer() { return this.r; } // Paper - OBFHELPER + + private RemoteStatusListener(IMinecraftServer iminecraftserver, int i) { + super("Query Listener"); +@@ -107,6 +108,7 @@ public class RemoteStatusListener extends RemoteConnectionThread { + + remotestatusreply.a((int) 0); + remotestatusreply.a(this.a(datagrampacket.getSocketAddress())); ++ /* Paper start - GS4 Query event + remotestatusreply.a(this.i); + remotestatusreply.a("SMP"); + remotestatusreply.a(this.j); +@@ -114,6 +116,31 @@ public class RemoteStatusListener extends RemoteConnectionThread { + remotestatusreply.a(Integer.toString(this.h)); + remotestatusreply.a((short) this.g); + remotestatusreply.a(this.m); ++ */ ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType = ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.BASIC; ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder() ++ .motd(this.getMotd()) ++ .map(this.getWorldName()) ++ .currentPlayers(this.getServer().getPlayerCount()) ++ .maxPlayers(this.getMaxPlayers()) ++ .port(this.getServerPort()) ++ .hostname(this.getServerHost()) ++ .gameVersion(this.getServer().getVersion()) ++ .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion()) ++ .build(); ++ com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent = ++ new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, datagrampacket.getAddress(), queryResponse); ++ queryEvent.callEvent(); ++ queryResponse = queryEvent.getResponse(); ++ remotestatusreply.writeString(queryResponse.getMotd()); ++ remotestatusreply.writeString("SMP"); ++ remotestatusreply.writeString(queryResponse.getMap()); ++ remotestatusreply.writeString(Integer.toString(queryResponse.getCurrentPlayers())); ++ remotestatusreply.writeString(Integer.toString(queryResponse.getMaxPlayers())); ++ remotestatusreply.writeShort((short) queryResponse.getPort()); ++ remotestatusreply.writeString(queryResponse.getHostname()); ++ // Paper end + this.a(remotestatusreply.a(), datagrampacket); + RemoteStatusListener.LOGGER.debug("Status [{}]", socketaddress); + } +@@ -150,6 +177,7 @@ public class RemoteStatusListener extends RemoteConnectionThread { + this.p.a("splitnum"); + this.p.a((int) 128); + this.p.a((int) 0); ++ /* Paper start - GS4 Query event + this.p.a("hostname"); + this.p.a(this.i); + this.p.a("gametype"); +@@ -185,6 +213,79 @@ public class RemoteStatusListener extends RemoteConnectionThread { + } + + this.p.a((int) 0); ++ */ ++ // Pack plugins ++ java.util.List plugins = java.util.Collections.emptyList(); ++ org.bukkit.plugin.Plugin[] bukkitPlugins; ++ if (((DedicatedServer) this.getServer()).server.getQueryPlugins() && (bukkitPlugins = org.bukkit.Bukkit.getPluginManager().getPlugins()).length > 0) { ++ plugins = java.util.stream.Stream.of(bukkitPlugins) ++ .map(plugin -> com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation.of(plugin.getName(), plugin.getDescription().getVersion())) ++ .collect(java.util.stream.Collectors.toList()); ++ } ++ ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder() ++ .motd(this.getMotd()) ++ .map(this.getWorldName()) ++ .currentPlayers(this.getServer().getPlayerCount()) ++ .maxPlayers(this.getMaxPlayers()) ++ .port(this.getServerPort()) ++ .hostname(this.getServerHost()) ++ .plugins(plugins) ++ .players(this.getServer().getPlayers()) ++ .gameVersion(this.getServer().getVersion()) ++ .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion()) ++ .build(); ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType = ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.FULL; ++ com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent = ++ new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, datagrampacket.getAddress(), queryResponse); ++ queryEvent.callEvent(); ++ queryResponse = queryEvent.getResponse(); ++ this.getCachedFullResponse().writeString("hostname"); ++ this.getCachedFullResponse().writeString(queryResponse.getMotd()); ++ this.getCachedFullResponse().writeString("gametype"); ++ this.getCachedFullResponse().writeString("SMP"); ++ this.getCachedFullResponse().writeString("game_id"); ++ this.getCachedFullResponse().writeString("MINECRAFT"); ++ this.getCachedFullResponse().writeString("version"); ++ this.getCachedFullResponse().writeString(queryResponse.getGameVersion()); ++ this.getCachedFullResponse().writeString("plugins"); ++ java.lang.StringBuilder pluginsString = new java.lang.StringBuilder(); ++ pluginsString.append(queryResponse.getServerVersion()); ++ if (!queryResponse.getPlugins().isEmpty()) { ++ pluginsString.append(": "); ++ java.util.Iterator iter = queryResponse.getPlugins().iterator(); ++ while (iter.hasNext()) { ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation info = iter.next(); ++ pluginsString.append(info.getName()); ++ if (info.getVersion() != null) { ++ pluginsString.append(' ').append(info.getVersion().replace(";", ",")); ++ } ++ if (iter.hasNext()) { ++ pluginsString.append(';').append(' '); ++ } ++ } ++ } ++ this.getCachedFullResponse().writeString(pluginsString.toString()); ++ this.getCachedFullResponse().writeString("map"); ++ this.getCachedFullResponse().writeString(queryResponse.getMap()); ++ this.getCachedFullResponse().writeString("numplayers"); ++ this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getCurrentPlayers())); ++ this.getCachedFullResponse().writeString("maxplayers"); ++ this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getMaxPlayers())); ++ this.getCachedFullResponse().writeString("hostport"); ++ this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getPort())); ++ this.getCachedFullResponse().writeString("hostip"); ++ this.getCachedFullResponse().writeString(queryResponse.getHostname()); ++ // The "meaningless data" start, copied from above ++ this.getCachedFullResponse().writeInt(0); ++ this.getCachedFullResponse().writeInt(1); ++ this.getCachedFullResponse().writeString("player_"); ++ this.getCachedFullResponse().writeInt(0); ++ // "Meaningless data" end ++ queryResponse.getPlayers().forEach(this.getCachedFullResponse()::writeStringUnchecked); ++ this.getCachedFullResponse().writeInt(0); ++ // Paper end + return this.p.a(); + } + } diff --git a/patches/server-unmapped/0001/0642-Added-PlayerLoomPatternSelectEvent.patch b/patches/server-unmapped/0001/0642-Added-PlayerLoomPatternSelectEvent.patch new file mode 100644 index 0000000000..c6d4f6121a --- /dev/null +++ b/patches/server-unmapped/0001/0642-Added-PlayerLoomPatternSelectEvent.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 25 Nov 2020 16:33:27 -0800 +Subject: [PATCH] Added PlayerLoomPatternSelectEvent + + +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerLoom.java b/src/main/java/net/minecraft/world/inventory/ContainerLoom.java +index 7980930cc712e37a788f894bf2d2ee2b1cfc1196..5e5367710d43fc421806bda31cd611a9cb5869f3 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerLoom.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerLoom.java +@@ -22,6 +22,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryLoom; + import org.bukkit.craftbukkit.inventory.CraftInventoryView; + import org.bukkit.entity.Player; + // CraftBukkit end ++import io.papermc.paper.event.player.PlayerLoomPatternSelectEvent; // Paper + + public class ContainerLoom extends Container { + +@@ -41,7 +42,7 @@ public class ContainerLoom extends Container { + } + // CraftBukkit end + private final ContainerAccess containerAccess; +- private final ContainerProperty d; ++ private final ContainerProperty d; public final ContainerProperty getSelectedBannerPattern() { return this.d; }; // Paper - OBFHELPER + private Runnable e; + private final Slot f; + private final Slot g; +@@ -160,7 +161,22 @@ public class ContainerLoom extends Container { + @Override + public boolean a(EntityHuman entityhuman, int i) { + if (i > 0 && i <= EnumBannerPatternType.R) { +- this.d.set(i); ++ // Paper start ++ int enumBannerPatternTypeOrdinal = i; ++ PlayerLoomPatternSelectEvent event = new PlayerLoomPatternSelectEvent((Player) entityhuman.getBukkitEntity(), ((CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.block.banner.PatternType.getByIdentifier(EnumBannerPatternType.values()[i].getIdentifier())); ++ if (!event.callEvent()) { ++ ((Player) entityhuman.getBukkitEntity()).updateInventory(); ++ return false; ++ } ++ for (EnumBannerPatternType nms : EnumBannerPatternType.values()) { ++ if (event.getPatternType().getIdentifier().equals(nms.getIdentifier())) { ++ enumBannerPatternTypeOrdinal = nms.ordinal(); ++ break; ++ } ++ } ++ ((Player) entityhuman.getBukkitEntity()).updateInventory(); ++ this.getSelectedBannerPattern().set(enumBannerPatternTypeOrdinal); ++ // Paper end + this.j(); + return true; + } else { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnumBannerPatternType.java b/src/main/java/net/minecraft/world/level/block/entity/EnumBannerPatternType.java +index 988e52c675dbb5ef368c8dbb5fb6d4229eb30174..9bc27e727d11d4ac9055a0bc02d8a2ef606ff9c4 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/EnumBannerPatternType.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/EnumBannerPatternType.java +@@ -33,6 +33,7 @@ public enum EnumBannerPatternType { + this.T = flag; + } + ++ public String getIdentifier() { return this.b(); } // Paper - OBFHELPER + public String b() { + return this.V; + } diff --git a/patches/server-unmapped/0001/0643-Configurable-door-breaking-difficulty.patch b/patches/server-unmapped/0001/0643-Configurable-door-breaking-difficulty.patch new file mode 100644 index 0000000000..5ae39b3398 --- /dev/null +++ b/patches/server-unmapped/0001/0643-Configurable-door-breaking-difficulty.patch @@ -0,0 +1,101 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 22:27:43 -0800 +Subject: [PATCH] Configurable door breaking difficulty + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index c42e5d9f9c9f67988383c4c25123d8a629ede9e3..946c12abc0e25ccfe09ee64a7ac8b045ba5c46a9 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -5,8 +5,12 @@ import java.util.EnumMap; + import java.util.HashMap; + import java.util.List; + import java.util.Map; ++import java.util.stream.Collectors; + + import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode; ++import net.minecraft.world.EnumDifficulty; ++import net.minecraft.world.entity.monster.EntityVindicator; ++import net.minecraft.world.entity.monster.EntityZombie; + import org.bukkit.Bukkit; + import org.bukkit.Material; + import org.bukkit.configuration.ConfigurationSection; +@@ -73,6 +77,11 @@ public class PaperWorldConfig { + return config.getString("world-settings." + worldName + "." + path, config.getString("world-settings.default." + path)); + } + ++ private > List getEnumList(String path, List def, Class type) { ++ config.addDefault("world-settings.default." + path, def.stream().map(Enum::name).collect(Collectors.toList())); ++ return ((List) (config.getList("world-settings." + worldName + "." + path, config.getList("world-settings.default." + path)))).stream().map(s -> Enum.valueOf(type, s)).collect(Collectors.toList()); ++ } ++ + public int cactusMaxHeight; + public int reedMaxHeight; + public int bambooMaxHeight; +@@ -733,4 +742,23 @@ public class PaperWorldConfig { + private void disableMobSpawnerSpawnEggTransformation() { + disableMobSpawnerSpawnEggTransformation = getBoolean("game-mechanics.disable-mob-spawner-spawn-egg-transformation", disableMobSpawnerSpawnEggTransformation); + } ++ ++ public List zombieBreakDoors; ++ public List vindicatorBreakDoors; ++ private void setupEntityBreakingDoors() { ++ zombieBreakDoors = getEnumList( ++ "door-breaking-difficulty.zombie", ++ Arrays.stream(EnumDifficulty.values()) ++ .filter(EntityZombie.getDoorBreakingPredicate()) ++ .collect(Collectors.toList()), ++ EnumDifficulty.class ++ ); ++ vindicatorBreakDoors = getEnumList( ++ "door-breaking-difficulty.vindicator", ++ Arrays.stream(EnumDifficulty.values()) ++ .filter(EntityVindicator.getDoorBreakingPredicate()) ++ .collect(Collectors.toList()), ++ EnumDifficulty.class ++ ); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java b/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java +index c45dcb56af95f3e87e292b92b697a336461f01bc..f0eda0b83bab8e3a8adbb569b5997402b0e08e9a 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityVindicator.java +@@ -48,6 +48,7 @@ import net.minecraft.world.level.WorldAccess; + + public class EntityVindicator extends EntityIllagerAbstract { + ++ public static final Predicate getDoorBreakingPredicate() { return b; } // Paper - OBFHELPER + private static final Predicate b = (enumdifficulty) -> { + return enumdifficulty == EnumDifficulty.NORMAL || enumdifficulty == EnumDifficulty.HARD; + }; +@@ -204,7 +205,7 @@ public class EntityVindicator extends EntityIllagerAbstract { + static class a extends PathfinderGoalBreakDoor { + + public a(EntityInsentient entityinsentient) { +- super(entityinsentient, 6, EntityVindicator.b); ++ super(entityinsentient, 6, com.google.common.base.Predicates.in(entityinsentient.world.paperConfig.vindicatorBreakDoors)); // Paper + this.a(EnumSet.of(PathfinderGoal.Type.MOVE)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +index fb98609a38d665659076b8949b59eaf084408a17..634416c354184bc6a2348c27c55e9868009ccd28 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityZombie.java +@@ -86,6 +86,7 @@ public class EntityZombie extends EntityMonster { + private static final DataWatcherObject d = DataWatcher.a(EntityZombie.class, DataWatcherRegistry.i); + private static final DataWatcherObject bo = DataWatcher.a(EntityZombie.class, DataWatcherRegistry.b); + public static final DataWatcherObject DROWN_CONVERTING = DataWatcher.a(EntityZombie.class, DataWatcherRegistry.i); ++ public static final Predicate getDoorBreakingPredicate() { return bq; } // Paper - OBFHELPER + private static final Predicate bq = (enumdifficulty) -> { + return enumdifficulty == EnumDifficulty.HARD; + }; +@@ -98,7 +99,7 @@ public class EntityZombie extends EntityMonster { + + public EntityZombie(EntityTypes entitytypes, World world) { + super(entitytypes, world); +- this.br = new PathfinderGoalBreakDoor(this, EntityZombie.bq); ++ this.br = new PathfinderGoalBreakDoor(this, com.google.common.base.Predicates.in(world.paperConfig.zombieBreakDoors)); // Paper + } + + public EntityZombie(World world) { diff --git a/patches/server-unmapped/0001/0644-Empty-commands-shall-not-be-dispatched.patch b/patches/server-unmapped/0001/0644-Empty-commands-shall-not-be-dispatched.patch new file mode 100644 index 0000000000..5cf58807ca --- /dev/null +++ b/patches/server-unmapped/0001/0644-Empty-commands-shall-not-be-dispatched.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 6 Jan 2021 23:38:43 +0100 +Subject: [PATCH] Empty commands shall not be dispatched + + +diff --git a/src/main/java/net/minecraft/commands/CommandDispatcher.java b/src/main/java/net/minecraft/commands/CommandDispatcher.java +index 07d3dec9f613013aac72f3f5db17089ebe5ee770..a70e0761aeddee8fafff971b5cbd0422ab560fb5 100644 +--- a/src/main/java/net/minecraft/commands/CommandDispatcher.java ++++ b/src/main/java/net/minecraft/commands/CommandDispatcher.java +@@ -223,6 +223,7 @@ public class CommandDispatcher { + command = event.getCommand(); + + String[] args = command.split(" "); ++ if (args.length == 0) return 0; // Paper - empty commands shall not be dispatched + + String cmd = args[0]; + if (cmd.startsWith("minecraft:")) cmd = cmd.substring("minecraft:".length()); diff --git a/patches/server-unmapped/0001/0645-Implement-API-to-expose-exact-interaction-point.patch b/patches/server-unmapped/0001/0645-Implement-API-to-expose-exact-interaction-point.patch new file mode 100644 index 0000000000..357baf61de --- /dev/null +++ b/patches/server-unmapped/0001/0645-Implement-API-to-expose-exact-interaction-point.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matthew Miller +Date: Mon, 4 Jan 2021 16:40:27 +1000 +Subject: [PATCH] Implement API to expose exact interaction point + + +diff --git a/src/main/java/net/minecraft/server/level/PlayerInteractManager.java b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +index d0a4b5cc21d40f17ed85cc174797e2705d7a0dc3..4203081692d2e4c43abc47aeb85f42b09acb7eee 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerInteractManager.java ++++ b/src/main/java/net/minecraft/server/level/PlayerInteractManager.java +@@ -495,7 +495,7 @@ public class PlayerInteractManager { + cancelledBlock = true; + } + +- PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(entityplayer, Action.RIGHT_CLICK_BLOCK, blockposition, movingobjectpositionblock.getDirection(), itemstack, cancelledBlock, enumhand); ++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(entityplayer, Action.RIGHT_CLICK_BLOCK, blockposition, movingobjectpositionblock.getDirection(), itemstack, cancelledBlock, enumhand, movingobjectpositionblock.getPos()); // Paper + firedInteract = true; + interactResult = event.useItemInHand() == Event.Result.DENY; + interactPosition = blockposition.immutableCopy(); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 93157e38f40af84341b8bb20598cf07118e723bc..d03a9c1946da672509c56416bab9c1878e37ddb7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -68,7 +68,9 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParameters; + import net.minecraft.world.phys.MovingObjectPosition; + import net.minecraft.world.phys.MovingObjectPositionBlock; + import net.minecraft.world.phys.MovingObjectPositionEntity; ++import net.minecraft.world.phys.Vec3D; + import org.bukkit.Bukkit; ++import org.bukkit.Location; // Paper + import org.bukkit.Material; + import org.bukkit.NamespacedKey; + import org.bukkit.Server; +@@ -469,7 +471,13 @@ public class CraftEventFactory { + return callPlayerInteractEvent(who, action, position, direction, itemstack, false, hand); + } + ++ // Paper start - Add interactionPoint + public static PlayerInteractEvent callPlayerInteractEvent(EntityHuman who, Action action, BlockPosition position, EnumDirection direction, ItemStack itemstack, boolean cancelledBlock, EnumHand hand) { ++ return callPlayerInteractEvent(who, action, position, direction, itemstack, cancelledBlock, hand, null); ++ } ++ ++ public static PlayerInteractEvent callPlayerInteractEvent(EntityHuman who, Action action, BlockPosition position, EnumDirection direction, ItemStack itemstack, boolean cancelledBlock, EnumHand hand, Vec3D hitVec) { ++ // Paper end + Player player = (who == null) ? null : (Player) who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); + +@@ -495,7 +503,10 @@ public class CraftEventFactory { + itemInHand = null; + } + +- PlayerInteractEvent event = new PlayerInteractEvent(player, action, itemInHand, blockClicked, blockFace, (hand == null) ? null : ((hand == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); ++ // Paper start ++ Location interactionPoint = hitVec == null ? null : new Location(craftWorld, hitVec.x, hitVec.y, hitVec.z); ++ PlayerInteractEvent event = new PlayerInteractEvent(player, action, itemInHand, blockClicked, blockFace, (hand == null) ? null : ((hand == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND), interactionPoint); ++ // Paper end + if (cancelledBlock) { + event.setUseInteractedBlock(Event.Result.DENY); + } diff --git a/patches/server-unmapped/0001/0646-Remove-stale-POIs.patch b/patches/server-unmapped/0001/0646-Remove-stale-POIs.patch new file mode 100644 index 0000000000..05bd91ba67 --- /dev/null +++ b/patches/server-unmapped/0001/0646-Remove-stale-POIs.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 9 Jan 2021 14:17:07 +0100 +Subject: [PATCH] Remove stale POIs + + +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index 4a87b9ebc2a584d8a2fca874342057e81fbbc1c6..c38ef337f9a662d689994a0d530e8e655b843177 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -2078,6 +2078,11 @@ public class WorldServer extends World implements GeneratorAccessSeed { + }); + optional1.ifPresent((villageplacetype) -> { + this.getMinecraftServer().execute(() -> { ++ // Paper start ++ if (!optional.isPresent() && this.getPoiStorage().test(blockposition1, com.google.common.base.Predicates.alwaysTrue())) { ++ this.getPoiStorage().remove(blockposition1); ++ } ++ // Paper end + this.y().a(blockposition1, villageplacetype); + PacketDebug.a(this, blockposition1); + }); +@@ -2085,6 +2090,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + } + ++ public final VillagePlace getPoiStorage() { return this.y(); } // Paper - OBFHELPER + public VillagePlace y() { + return this.getChunkProvider().j(); + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java +index 04b01cb841dc4f34ded5aaa4ea7a8e6d4b470183..ce165233739c7b92a76031b949f269bd0a11149c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java +@@ -54,6 +54,7 @@ public class VillagePlace extends RegionFileSection { + ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).a(blockposition, villageplacetype); + } + ++ public void remove(BlockPosition blockposition) { this.a(blockposition); } // Paper - OBFHELPER + public void a(BlockPosition blockposition) { + ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).a(blockposition); + } +@@ -138,6 +139,7 @@ public class VillagePlace extends RegionFileSection { + return ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).c(blockposition); + } + ++ public final boolean test(BlockPosition blockposition, Predicate predicate) { return this.a(blockposition, predicate); } // Paper - OBFHELPER + public boolean a(BlockPosition blockposition, Predicate predicate) { + return (Boolean) this.d(SectionPosition.a(blockposition).s()).map((villageplacesection) -> { + return villageplacesection.a(blockposition, predicate); diff --git a/patches/server-unmapped/0001/0647-Fix-villager-boat-exploit.patch b/patches/server-unmapped/0001/0647-Fix-villager-boat-exploit.patch new file mode 100644 index 0000000000..33fa1b3d4e --- /dev/null +++ b/patches/server-unmapped/0001/0647-Fix-villager-boat-exploit.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Mon, 11 Jan 2021 12:43:51 -0800 +Subject: [PATCH] Fix villager boat exploit + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index a7ca1dbebf30262d7c526f96b7a45482b1f898f4..0757cfcb96778258ba2593756e4ca9cbb16e2f87 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -77,6 +77,7 @@ import net.minecraft.util.MathHelper; + import net.minecraft.world.effect.MobEffect; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityTypes; ++import net.minecraft.world.entity.npc.EntityVillagerAbstract; + import net.minecraft.world.entity.player.EntityHuman; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.EnumGamemode; +@@ -621,6 +622,15 @@ public abstract class PlayerList { + + for (Iterator iterator = entity.getAllPassengers().iterator(); iterator.hasNext(); entity1.dead = true) { + entity1 = (Entity) iterator.next(); ++ // Paper start ++ if (entity1 instanceof EntityVillagerAbstract) { ++ final EntityVillagerAbstract villager = (EntityVillagerAbstract) entity1; ++ final EntityHuman human = villager.getTrader(); ++ if (human != null) { ++ villager.setTradingPlayer(null); ++ } ++ } ++ // Paper end + worldserver.removeEntity(entity1); + } + diff --git a/patches/server-unmapped/0001/0648-Entity-load-save-limit-per-chunk.patch b/patches/server-unmapped/0001/0648-Entity-load-save-limit-per-chunk.patch new file mode 100644 index 0000000000..a168fe3f5f --- /dev/null +++ b/patches/server-unmapped/0001/0648-Entity-load-save-limit-per-chunk.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Wed, 18 Nov 2020 20:52:25 -0800 +Subject: [PATCH] Entity load/save limit per chunk + +Adds a config option to limit the number of entities saved and loaded +to a chunk. The default values of -1 disable the limit. Although +defaults are only included for certain entites, this allows setting +limits for any entity type. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 946c12abc0e25ccfe09ee64a7ac8b045ba5c46a9..bda4eeb032bea452ea368c679f96b2bd93118730 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -9,6 +9,7 @@ import java.util.stream.Collectors; + + import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode; + import net.minecraft.world.EnumDifficulty; ++import net.minecraft.world.entity.EntityTypes; + import net.minecraft.world.entity.monster.EntityVindicator; + import net.minecraft.world.entity.monster.EntityZombie; + import org.bukkit.Bukkit; +@@ -761,4 +762,18 @@ public class PaperWorldConfig { + EnumDifficulty.class + ); + } ++ ++ public Map, Integer> entityPerChunkSaveLimits = new HashMap<>(); ++ private void entityPerChunkSaveLimits() { ++ getInt("entity-per-chunk-save-limit.experience_orb", -1); ++ getInt("entity-per-chunk-save-limit.snowball", -1); ++ getInt("entity-per-chunk-save-limit.ender_pearl", -1); ++ getInt("entity-per-chunk-save-limit.arrow", -1); ++ EntityTypes.getEntityNameList().forEach(name -> { ++ final EntityTypes type = EntityTypes.getByName(name.getKey()).orElseThrow(() -> new IllegalStateException("Unknown Entity Type: " + name.toString())); ++ final String path = ".entity-per-chunk-save-limit." + name.getKey(); ++ final int value = config.getInt("world-settings." + worldName + path, config.getInt("world-settings.default" + path, -1)); // get without setting defaults ++ if (value != -1) entityPerChunkSaveLimits.put(type, value); ++ }); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +index 2e5221bc1b9e260e33f2cef2653dc59d05e2680d..4eaf497d048324a85ce49fc1c6e9559991c20df7 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkRegionLoader.java +@@ -539,11 +539,22 @@ public class ChunkRegionLoader { + + chunk.d(false); + ++ // Paper start ++ final Map, Integer> savedEntityCounts = Maps.newHashMap(); + for (int j = 0; j < chunk.getEntitySlices().length; ++j) { + Iterator iterator1 = chunk.getEntitySlices()[j].iterator(); + + while (iterator1.hasNext()) { + Entity entity = (Entity) iterator1.next(); ++ final EntityTypes entityType = entity.getEntityType(); ++ final int saveLimit = worldserver.paperConfig.entityPerChunkSaveLimits.getOrDefault(entityType, -1); ++ if (saveLimit > -1) { ++ if (savedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) { ++ continue; ++ } ++ savedEntityCounts.merge(entityType, 1, Integer::sum); ++ } ++ // Paper end + NBTTagCompound nbttagcompound4 = new NBTTagCompound(); + // Paper start + if (asyncsavedata == null && !entity.dead && (int) Math.floor(entity.locX()) >> 4 != chunk.getPos().x || (int) Math.floor(entity.locZ()) >> 4 != chunk.getPos().z) { +@@ -674,10 +685,21 @@ public class ChunkRegionLoader { + NBTTagList nbttaglist = nbttagcompound.getList("Entities", 10); + World world = chunk.getWorld(); + ++ // Paper start ++ final Map, Integer> loadedEntityCounts = Maps.newHashMap(); + for (int i = 0; i < nbttaglist.size(); ++i) { + NBTTagCompound nbttagcompound1 = nbttaglist.getCompound(i); + + EntityTypes.a(nbttagcompound1, world, (entity) -> { ++ final EntityTypes entityType = entity.getEntityType(); ++ final int saveLimit = world.paperConfig.entityPerChunkSaveLimits.getOrDefault(entityType, -1); ++ if (saveLimit > -1) { ++ if (loadedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) { ++ return null; ++ } ++ loadedEntityCounts.merge(entityType, 1, Integer::sum); ++ } ++ // Paper end + chunk.a(entity); + return entity; + }); diff --git a/patches/server-unmapped/0001/0649-Add-sendOpLevel-API.patch b/patches/server-unmapped/0001/0649-Add-sendOpLevel-API.patch new file mode 100644 index 0000000000..e68f9baa60 --- /dev/null +++ b/patches/server-unmapped/0001/0649-Add-sendOpLevel-API.patch @@ -0,0 +1,51 @@ +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 0757cfcb96778258ba2593756e4ca9cbb16e2f87..24b3a893a2b76a4ecfbc6b2cc1eac242e5c6e9d6 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1138,6 +1138,11 @@ public abstract class PlayerList { + } + + private void a(EntityPlayer entityplayer, int i) { ++ // Paper start - add recalculatePermissions parameter ++ this.sendPlayerOperatorStatus(entityplayer, i, true); ++ } ++ public void sendPlayerOperatorStatus(EntityPlayer entityplayer, int i, boolean recalculatePermissions) { ++ // Paper end + if (entityplayer.playerConnection != null) { + byte b0; + +@@ -1152,8 +1157,10 @@ public abstract class PlayerList { + entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityStatus(entityplayer, b0)); + } + ++ if (recalculatePermissions) { // Paper + entityplayer.getBukkitEntity().recalculatePermissions(); // CraftBukkit + this.server.getCommandDispatcher().a(entityplayer); ++ } // Paper + } + + // Paper start +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 5a245184d6290a58bb9aed139cc4c7b5511ce491..e97ad53eb8bdc4c71d8014d060710cb3a29ab7f8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2297,6 +2297,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + ? (org.bukkit.entity.Firework) entity.getBukkitEntity() + : null; + } ++ ++ @Override ++ public void sendOpLevel(byte level) { ++ Preconditions.checkArgument(level >= 0 && level <= 4, "Level must be within [0, 4]"); ++ ++ this.getHandle().getMinecraftServer().getPlayerList().sendPlayerOperatorStatus(this.getHandle(), level, false); ++ } + // Paper end + + // Spigot start diff --git a/patches/server-unmapped/0001/0650-Add-StructureLocateEvent.patch b/patches/server-unmapped/0001/0650-Add-StructureLocateEvent.patch new file mode 100644 index 0000000000..3e68e76756 --- /dev/null +++ b/patches/server-unmapped/0001/0650-Add-StructureLocateEvent.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dfsek +Date: Wed, 16 Sep 2020 01:12:29 -0700 +Subject: [PATCH] Add StructureLocateEvent + + +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 b1f2c424ef358f79c7fced88a61560d3ce32a2b6..f2621f61cb372ec436fe81e7a93f1aef7d360f3f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -2,6 +2,7 @@ package net.minecraft.world.level.chunk; + + import com.google.common.collect.Lists; + import com.mojang.serialization.Codec; ++import io.papermc.paper.event.world.StructureLocateEvent; // Paper - Add import due to naming conflict. + import java.util.BitSet; + import java.util.Iterator; + import java.util.List; +@@ -160,6 +161,22 @@ public abstract class ChunkGenerator { + + @Nullable + public BlockPosition findNearestMapFeature(WorldServer worldserver, StructureGenerator structuregenerator, BlockPosition blockposition, int i, boolean flag) { ++ // Paper start ++ org.bukkit.World world = worldserver.getWorld(); ++ org.bukkit.Location originLocation = new org.bukkit.Location(world, blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ StructureLocateEvent event = new StructureLocateEvent(world, originLocation, org.bukkit.StructureType.getStructureTypes().get(structuregenerator.i()), i, flag); ++ if(!event.callEvent()) return null; ++ // If event call set a final location, skip structure finding and just return set result. ++ if(event.getResult() != null) return new BlockPosition(event.getResult().getBlockX(), event.getResult().getBlockY(), event.getResult().getBlockZ()); ++ // Get origin location (re)defined by event call. ++ blockposition = new BlockPosition(event.getOrigin().getBlockX(), event.getOrigin().getBlockY(), event.getOrigin().getBlockZ()); ++ // Get world (re)defined by event call. ++ worldserver = ((org.bukkit.craftbukkit.CraftWorld) event.getOrigin().getWorld()).getHandle(); ++ // Get radius and whether to find unexplored structures (re)defined by event call. ++ i = event.getRadius(); ++ flag = event.shouldFindUnexplored(); ++ structuregenerator = StructureGenerator.a.get(event.getType().getName()); ++ // Paper end + if (!this.b.a(structuregenerator)) { + return null; + } else if (structuregenerator == StructureGenerator.STRONGHOLD) { diff --git a/patches/server-unmapped/0001/0651-Collision-option-for-requiring-a-player-participant.patch b/patches/server-unmapped/0001/0651-Collision-option-for-requiring-a-player-participant.patch new file mode 100644 index 0000000000..2ffec6957f --- /dev/null +++ b/patches/server-unmapped/0001/0651-Collision-option-for-requiring-a-player-participant.patch @@ -0,0 +1,81 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index bda4eeb032bea452ea368c679f96b2bd93118730..5801fe872aff240dc8209069b978d6a4dba30f06 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -716,6 +716,18 @@ public class PaperWorldConfig { + } + } + ++ public boolean onlyPlayersCollide = false; ++ public boolean allowVehicleCollisions = true; ++ private void onlyPlayersCollide() { ++ onlyPlayersCollide = getBoolean("only-players-collide", onlyPlayersCollide); ++ allowVehicleCollisions = getBoolean("allow-vehicle-collisions", allowVehicleCollisions); ++ if (onlyPlayersCollide && !allowVehicleCollisions) { ++ log("Collisions will only work if a player is one of the two entities colliding."); ++ } else if (onlyPlayersCollide) { ++ log("Collisions will only work if a player OR a vehicle is one of the two entities colliding."); ++ } ++ } ++ + public int wanderingTraderSpawnMinuteTicks = 1200; + public int wanderingTraderSpawnDayTicks = 24000; + public int wanderingTraderSpawnChanceFailureIncrement = 25; +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index d801486565865cf3b56e9d80b7c1643e9b0f4597..f7223f214f911dd25abcf3a52745588ec630241d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1467,6 +1467,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + public void collide(Entity entity) { + if (!this.isSameVehicle(entity)) { + if (!entity.noclip && !this.noclip) { ++ if (this.world.paperConfig.onlyPlayersCollide && !(entity instanceof EntityPlayer || this instanceof EntityPlayer)) return; // Paper + double d0 = entity.locX() - this.locX(); + double d1 = entity.locZ() - this.locZ(); + double d2 = MathHelper.a(d0, d1); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java +index 2609b83573e0e8532e6c4c36d4f475bf0da6a354..069076d3c7165440217a7632b089ab2aa0fbdb1d 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java +@@ -14,6 +14,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity; + import net.minecraft.network.syncher.DataWatcher; + import net.minecraft.network.syncher.DataWatcherObject; + import net.minecraft.network.syncher.DataWatcherRegistry; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.sounds.SoundEffect; + import net.minecraft.sounds.SoundEffects; + import net.minecraft.tags.Tag; +@@ -230,6 +231,7 @@ public class EntityBoat extends Entity { + + @Override + public void collide(Entity entity) { ++ if (!this.world.paperConfig.allowVehicleCollisions && this.world.paperConfig.onlyPlayersCollide && !(entity instanceof EntityPlayer)) return; // Paper + if (entity instanceof EntityBoat) { + if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) { + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java +index 2e3ceab3e34f7756764b3471b13d48d1263ecba9..57821301ef031995e6044a17b46c70a693322455 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java +@@ -21,6 +21,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity; + import net.minecraft.network.syncher.DataWatcher; + import net.minecraft.network.syncher.DataWatcherObject; + import net.minecraft.network.syncher.DataWatcherRegistry; ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.tags.Tag; + import net.minecraft.tags.TagsBlock; + import net.minecraft.util.MathHelper; +@@ -767,6 +768,7 @@ public abstract class EntityMinecartAbstract extends Entity { + public void collide(Entity entity) { + if (!this.world.isClientSide) { + if (!entity.noclip && !this.noclip) { ++ if (!this.world.paperConfig.allowVehicleCollisions && this.world.paperConfig.onlyPlayersCollide && !(entity instanceof EntityPlayer)) return; // Paper + if (!this.w(entity)) { + // CraftBukkit start + VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity()); diff --git a/patches/server-unmapped/0001/0652-Make-ProjectileHitEvent-Cancellable.patch b/patches/server-unmapped/0001/0652-Make-ProjectileHitEvent-Cancellable.patch new file mode 100644 index 0000000000..abda75eef4 --- /dev/null +++ b/patches/server-unmapped/0001/0652-Make-ProjectileHitEvent-Cancellable.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 16 Jan 2021 14:30:12 -0500 +Subject: [PATCH] Make ProjectileHitEvent Cancellable + +Allows cancelling things like detonating TNT from Fire Arrows + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityFireball.java b/src/main/java/net/minecraft/world/entity/projectile/EntityFireball.java +index ede7b4dbf2dce7bac83c5e17eecfdaf0e8a84fe7..b9680f6f2e30ec9397d6a9c83e79563d9253aff6 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EntityFireball.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EntityFireball.java +@@ -87,7 +87,7 @@ public abstract class EntityFireball extends IProjectile { + + // CraftBukkit start - Fire ProjectileHitEvent + if (this.dead) { +- CraftEventFactory.callProjectileHitEvent(this, movingobjectposition); ++ if (!CraftEventFactory.callProjectileHitEvent(this, movingobjectposition)) return; // Paper - this is an undesired duplicate event, but make cancellable + } + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java +index 37b1febb45b900dfe4b225152e66bc4be83df220..9f2e7d345d98f50e6d47cbf4bb35714852fa42da 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/IProjectile.java +@@ -130,7 +130,7 @@ public abstract class IProjectile extends Entity { + } + + protected void a(MovingObjectPosition movingobjectposition) { +- org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition); // CraftBukkit - Call event ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition)) return; // CraftBukkit - Call event // Paper - make cancellable + MovingObjectPosition.EnumMovingObjectType movingobjectposition_enummovingobjecttype = movingobjectposition.getType(); + + if (movingobjectposition_enummovingobjecttype == MovingObjectPosition.EnumMovingObjectType.ENTITY) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index d03a9c1946da672509c56416bab9c1878e37ddb7..22da2cc26fc526d7222f64c296eaf26cabd23626 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1298,9 +1298,9 @@ public class CraftEventFactory { + return event; + } + +- public static void callProjectileHitEvent(Entity entity, MovingObjectPosition position) { ++ public static boolean callProjectileHitEvent(Entity entity, MovingObjectPosition position) { // Paper - make cancellable + if (position.getType() == MovingObjectPosition.EnumMovingObjectType.MISS) { +- return; ++ return false; // Paper - make cancellable + } + + Block hitBlock = null; +@@ -1317,7 +1317,7 @@ public class CraftEventFactory { + } + + ProjectileHitEvent event = new ProjectileHitEvent((Projectile) entity.getBukkitEntity(), hitEntity, hitBlock, hitFace); +- entity.world.getServer().getPluginManager().callEvent(event); ++ return event.callEvent(); // Paper + } + + public static ExpBottleEvent callExpBottleEvent(Entity entity, int exp) { diff --git a/patches/server-unmapped/0001/0653-Return-chat-component-with-empty-text-instead-of-thr.patch b/patches/server-unmapped/0001/0653-Return-chat-component-with-empty-text-instead-of-thr.patch new file mode 100644 index 0000000000..812ef9c07e --- /dev/null +++ b/patches/server-unmapped/0001/0653-Return-chat-component-with-empty-text-instead-of-thr.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: CDFN +Date: Tue, 7 Jul 2020 17:53:23 +0200 +Subject: [PATCH] Return chat component with empty text instead of throwing + exception + + +diff --git a/src/main/java/net/minecraft/world/inventory/Container.java b/src/main/java/net/minecraft/world/inventory/Container.java +index 9c6330da4e026a7753698b5d103c009730154c3e..bdd00608a72dd81003731ff5fbe774dfdc5220e5 100644 +--- a/src/main/java/net/minecraft/world/inventory/Container.java ++++ b/src/main/java/net/minecraft/world/inventory/Container.java +@@ -11,6 +11,7 @@ import net.minecraft.CrashReportSystemDetails; + import net.minecraft.ReportedException; + import net.minecraft.core.IRegistry; + import net.minecraft.core.NonNullList; ++import net.minecraft.network.chat.ChatComponentText; + import net.minecraft.server.level.EntityPlayer; + import net.minecraft.util.MathHelper; + import net.minecraft.world.IInventory; +@@ -61,7 +62,12 @@ public abstract class Container { + } + private IChatBaseComponent title; + public final IChatBaseComponent getTitle() { +- Preconditions.checkState(this.title != null, "Title not set"); ++ // Paper start - return chat component with empty text instead of throwing error ++ // Preconditions.checkState(this.title != null, "Title not set"); ++ if(this.title == null){ ++ return new ChatComponentText(""); ++ } ++ // Paper end + return this.title; + } + public final void setTitle(IChatBaseComponent title) { diff --git a/patches/server-unmapped/0001/0654-Make-schedule-command-per-world.patch b/patches/server-unmapped/0001/0654-Make-schedule-command-per-world.patch new file mode 100644 index 0000000000..87d12b6191 --- /dev/null +++ b/patches/server-unmapped/0001/0654-Make-schedule-command-per-world.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jan 2021 19:52:44 -0800 +Subject: [PATCH] Make schedule command per-world + + +diff --git a/src/main/java/net/minecraft/server/commands/CommandSchedule.java b/src/main/java/net/minecraft/server/commands/CommandSchedule.java +index b88a91072032b75f83d811d63e1b5e3808faa9be..440b25dde0299037032d535c4c751f252775d6ad 100644 +--- a/src/main/java/net/minecraft/server/commands/CommandSchedule.java ++++ b/src/main/java/net/minecraft/server/commands/CommandSchedule.java +@@ -32,7 +32,7 @@ public class CommandSchedule { + return new ChatMessage("commands.schedule.cleared.failure", new Object[]{object}); + }); + private static final SuggestionProvider c = (commandcontext, suggestionsbuilder) -> { +- return ICompletionProvider.b((Iterable) ((CommandListenerWrapper) commandcontext.getSource()).getServer().getSaveData().H().u().a(), suggestionsbuilder); ++ return ICompletionProvider.b((Iterable) ((CommandListenerWrapper) commandcontext.getSource()).getWorld().worldDataServer.H().u().a(), suggestionsbuilder); // Paper + }; + + public static void a(CommandDispatcher commanddispatcher) { +@@ -55,7 +55,7 @@ public class CommandSchedule { + } else { + long j = commandlistenerwrapper.getWorld().getTime() + (long) i; + MinecraftKey minecraftkey = (MinecraftKey) pair.getFirst(); +- CustomFunctionCallbackTimerQueue customfunctioncallbacktimerqueue = commandlistenerwrapper.getServer().getSaveData().H().u(); ++ CustomFunctionCallbackTimerQueue customfunctioncallbacktimerqueue = commandlistenerwrapper.getWorld().worldDataServer.H().u(); // Paper + + ((Either) pair.getSecond()).ifLeft((customfunction) -> { + String s = minecraftkey.toString(); +@@ -81,7 +81,7 @@ public class CommandSchedule { + } + + private static int a(CommandListenerWrapper commandlistenerwrapper, String s) throws CommandSyntaxException { +- int i = commandlistenerwrapper.getServer().getSaveData().H().u().a(s); ++ int i = commandlistenerwrapper.getWorld().worldDataServer.H().u().a(s); // Paper + + if (i == 0) { + throw CommandSchedule.b.create(s); diff --git a/patches/server-unmapped/0001/0655-Configurable-max-leash-distance.patch b/patches/server-unmapped/0001/0655-Configurable-max-leash-distance.patch new file mode 100644 index 0000000000..cab50cf8eb --- /dev/null +++ b/patches/server-unmapped/0001/0655-Configurable-max-leash-distance.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 21:04:03 -0800 +Subject: [PATCH] Configurable max leash distance + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 5801fe872aff240dc8209069b978d6a4dba30f06..849603de7c918788f1f80d7effb93658a69b0fd6 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -167,6 +167,12 @@ public class PaperWorldConfig { + } + } + ++ public float maxLeashDistance = 10f; ++ private void maxLeashDistance() { ++ maxLeashDistance = getFloat("max-leash-distance", maxLeashDistance); ++ log("Max leash distance: " + maxLeashDistance); ++ } ++ + public boolean disableEndCredits; + private void disableEndCredits() { + disableEndCredits = getBoolean("game-mechanics.disable-end-credits", false); +diff --git a/src/main/java/net/minecraft/world/entity/EntityCreature.java b/src/main/java/net/minecraft/world/entity/EntityCreature.java +index bbf0f345bfdd8a3a1f7fe902a42b2b18cdcf07a5..20570580367697e37e6c45147168c3beb6c8d31b 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityCreature.java ++++ b/src/main/java/net/minecraft/world/entity/EntityCreature.java +@@ -47,7 +47,7 @@ public abstract class EntityCreature extends EntityInsentient { + float f = this.g(entity); + + if (this instanceof EntityTameableAnimal && ((EntityTameableAnimal) this).isSitting()) { +- if (f > 10.0F) { ++ if (f > entity.world.paperConfig.maxLeashDistance) { // Paper + this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit + this.unleash(true, true); + } +@@ -56,7 +56,7 @@ public abstract class EntityCreature extends EntityInsentient { + } + + this.x(f); +- if (f > 10.0F) { ++ if (f > entity.world.paperConfig.maxLeashDistance) { // Paper + this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit + this.unleash(true, true); + this.goalSelector.a(PathfinderGoal.Type.MOVE); diff --git a/patches/server-unmapped/0001/0656-Implement-BlockPreDispenseEvent.patch b/patches/server-unmapped/0001/0656-Implement-BlockPreDispenseEvent.patch new file mode 100644 index 0000000000..bf883f3f6a --- /dev/null +++ b/patches/server-unmapped/0001/0656-Implement-BlockPreDispenseEvent.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matthew Miller +Date: Sun, 17 Jan 2021 13:16:09 +1000 +Subject: [PATCH] Implement BlockPreDispenseEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockDispenser.java b/src/main/java/net/minecraft/world/level/block/BlockDispenser.java +index 966051ab3e720e5b3f0fb9ab852c8908c5f23f3b..9b92824f1c2797e321ced953d33d2c2fd339374a 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockDispenser.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockDispenser.java +@@ -88,6 +88,7 @@ public class BlockDispenser extends BlockTileEntity { + IDispenseBehavior idispensebehavior = this.a(itemstack); + + if (idispensebehavior != IDispenseBehavior.NONE) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(worldserver, blockposition, itemstack, i)) return; // Paper - BlockPreDispenseEvent is called here + eventFired = false; // CraftBukkit - reset event status + tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack)); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 22da2cc26fc526d7222f64c296eaf26cabd23626..47906539c3e6cd7f34c0880a0bab2a185d79b71c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -69,6 +69,7 @@ import net.minecraft.world.phys.MovingObjectPosition; + import net.minecraft.world.phys.MovingObjectPositionBlock; + import net.minecraft.world.phys.MovingObjectPositionEntity; + import net.minecraft.world.phys.Vec3D; ++import io.papermc.paper.event.block.BlockPreDispenseEvent; // Paper + import org.bukkit.Bukkit; + import org.bukkit.Location; // Paper + import org.bukkit.Material; +@@ -1780,5 +1781,11 @@ public class CraftEventFactory { + BlockFailedDispenseEvent event = new BlockFailedDispenseEvent(block); + return event.callEvent(); + } ++ ++ public static boolean handleBlockPreDispenseEvent(WorldServer worldserver, BlockPosition blockposition, ItemStack itemStack, int slot) { ++ org.bukkit.block.Block block = worldserver.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ BlockPreDispenseEvent event = new BlockPreDispenseEvent(block, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), slot); ++ return event.callEvent(); ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0657-Added-Vanilla-Entity-Tags.patch b/patches/server-unmapped/0001/0657-Added-Vanilla-Entity-Tags.patch new file mode 100644 index 0000000000..900a0b16f9 --- /dev/null +++ b/patches/server-unmapped/0001/0657-Added-Vanilla-Entity-Tags.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 20:03:35 -0800 +Subject: [PATCH] Added Vanilla Entity Tags + + +diff --git a/src/main/java/io/papermc/paper/CraftEntityTag.java b/src/main/java/io/papermc/paper/CraftEntityTag.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2ca8e1bade5450a14125b77540792e0b18c3e19b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/CraftEntityTag.java +@@ -0,0 +1,29 @@ ++package io.papermc.paper; ++ ++import net.minecraft.resources.MinecraftKey; ++import net.minecraft.tags.Tags; ++import net.minecraft.world.entity.EntityTypes; ++import org.bukkit.craftbukkit.tag.CraftTag; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.entity.EntityType; ++ ++import java.util.Collections; ++import java.util.Set; ++import java.util.stream.Collectors; ++ ++public class CraftEntityTag extends CraftTag, EntityType> { ++ ++ public CraftEntityTag(Tags> registry, MinecraftKey tag) { ++ super(registry, tag); ++ } ++ ++ @Override ++ public boolean isTagged(EntityType item) { ++ return getHandle().isTagged(CraftMagicNumbers.getEntityTypes(item)); ++ } ++ ++ @Override ++ public Set getValues() { ++ return Collections.unmodifiableSet(getHandle().getTagged().stream().map(CraftMagicNumbers::getEntityType).collect(Collectors.toSet())); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index fe2ad3f9298af4d405711b4798e34ae484a19db9..a4755e2cdb40afe6af47435f92963a53d7b719c7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2216,6 +2216,11 @@ public final class CraftServer implements Server { + Preconditions.checkArgument(clazz == org.bukkit.Fluid.class, "Fluid namespace must have fluid type"); + + return (org.bukkit.Tag) new CraftFluidTag(console.getTagRegistry().getFluidTags(), key); ++ // Paper start ++ case org.bukkit.Tag.REGISTRY_ENTITIES: ++ Preconditions.checkArgument(clazz == org.bukkit.entity.EntityType.class, "Entity namespace must have entitytype type"); ++ return (org.bukkit.Tag) new io.papermc.paper.CraftEntityTag(console.getTagRegistry().getEntityTags(), key); ++ // Paper end + default: + throw new IllegalArgumentException(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 74ebd6257ca7c87bcedff831d213273e7d542612..f69b4576f05dbf763e99d5d1cbed069c55c793ed 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -118,8 +118,17 @@ public final class CraftMagicNumbers implements UnsafeValues { + private static final Map MATERIAL_ITEM = new HashMap<>(); + private static final Map MATERIAL_BLOCK = new HashMap<>(); + private static final Map MATERIAL_FLUID = new HashMap<>(); ++ // Paper start ++ private static final Map> ENTITY_TYPE_ENTITY_TYPES = new HashMap<>(); ++ private static final Map, org.bukkit.entity.EntityType> ENTITY_TYPES_ENTITY_TYPE = new HashMap<>(); + + static { ++ for (org.bukkit.entity.EntityType type : org.bukkit.entity.EntityType.values()) { ++ if (type == org.bukkit.entity.EntityType.UNKNOWN) continue; ++ ENTITY_TYPE_ENTITY_TYPES.put(type, IRegistry.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(type.getKey()))); ++ ENTITY_TYPES_ENTITY_TYPE.put(IRegistry.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(type.getKey())), type); ++ } ++ // Paper end + for (Block block : IRegistry.BLOCK) { + BLOCK_MATERIAL.put(block, Material.getMaterial(IRegistry.BLOCK.getKey(block).getKey().toUpperCase(Locale.ROOT))); + } +@@ -185,6 +194,14 @@ public final class CraftMagicNumbers implements UnsafeValues { + public static MinecraftKey key(Material mat) { + return CraftNamespacedKey.toMinecraft(mat.getKey()); + } ++ // Paper start ++ public static net.minecraft.world.entity.EntityTypes getEntityTypes(org.bukkit.entity.EntityType type) { ++ return ENTITY_TYPE_ENTITY_TYPES.get(type); ++ } ++ public static org.bukkit.entity.EntityType getEntityType(net.minecraft.world.entity.EntityTypes entityTypes) { ++ return ENTITY_TYPES_ENTITY_TYPE.get(entityTypes); ++ } ++ // Paper end + // ======================================================================== + // Paper start + @Override diff --git a/patches/server-unmapped/0001/0658-added-Wither-API.patch b/patches/server-unmapped/0001/0658-added-Wither-API.patch new file mode 100644 index 0000000000..29bb22ea9d --- /dev/null +++ b/patches/server-unmapped/0001/0658-added-Wither-API.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 5 Jul 2020 15:39:19 -0700 +Subject: [PATCH] added Wither API + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java +index 30290c0208a4725b2eb0e7764465c354e592e4ee..891905712903bf3ba241187791cfa995375430d5 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java +@@ -82,6 +82,11 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + return entityliving.getMonsterType() != EnumMonsterType.UNDEAD && entityliving.ei(); + }; + private static final PathfinderTargetCondition bz = (new PathfinderTargetCondition()).a(20.0D).a(EntityWither.by); ++ // Paper start ++ private boolean canPortal = false; ++ ++ public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; } ++ // Paper end + + public EntityWither(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -580,6 +585,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + this.datawatcher.set((DataWatcherObject) EntityWither.bo.get(i), j); + } + ++ public final boolean isPowered() { return this.S_(); } // Paper - OBFHELPER + public boolean S_() { + return this.getHealth() <= this.getMaxHealth() / 2.0F; + } +@@ -596,7 +602,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + + @Override + public boolean canPortal() { +- return false; ++ return canPortal; // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +index 1d94aeec37dcb9758d88ef25a5cad1333bbfbf6c..adf4ce8afa3a42ea8a184905969e03b6b756830f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +@@ -38,4 +38,31 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok + public BossBar getBossBar() { + return bossBar; + } ++ ++ // Paper start ++ @Override ++ public boolean isCharged() { ++ return getHandle().isPowered(); ++ } ++ ++ @Override ++ public int getInvulnerableTicks() { ++ return getHandle().getInvul(); ++ } ++ ++ @Override ++ public void setInvulnerableTicks(int ticks) { ++ getHandle().setInvul(ticks); ++ } ++ ++ @Override ++ public boolean canTravelThroughPortals() { ++ return getHandle().canPortal(); ++ } ++ ++ @Override ++ public void setCanTravelThroughPortals(boolean value) { ++ getHandle().setCanTravelThroughPortals(value); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0659-Added-firing-of-PlayerChangeBeaconEffectEvent.patch b/patches/server-unmapped/0001/0659-Added-firing-of-PlayerChangeBeaconEffectEvent.patch new file mode 100644 index 0000000000..8300eca18e --- /dev/null +++ b/patches/server-unmapped/0001/0659-Added-firing-of-PlayerChangeBeaconEffectEvent.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 24 Jun 2020 15:14:51 -0600 +Subject: [PATCH] Added firing of PlayerChangeBeaconEffectEvent + + +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerBeacon.java b/src/main/java/net/minecraft/world/inventory/ContainerBeacon.java +index 7e5a0bad616d9477e01426ab1604184ef3fab1c1..085298a9c3837dfa48dfbbf02d6261db12c79036 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerBeacon.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerBeacon.java +@@ -13,6 +13,10 @@ import net.minecraft.world.entity.player.PlayerInventory; + import org.bukkit.craftbukkit.inventory.CraftInventoryView; + // CraftBukkit end + ++// Paper start ++import io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent; ++// Paper end ++ + public class ContainerBeacon extends Container { + + private final IInventory beacon; +@@ -139,9 +143,15 @@ public class ContainerBeacon extends Container { + + public void c(int i, int j) { + if (this.d.hasItem()) { +- this.containerProperties.setProperty(1, i); +- this.containerProperties.setProperty(2, j); ++ // Paper start ++ PlayerChangeBeaconEffectEvent event = new PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), org.bukkit.potion.PotionEffectType.getById(i), org.bukkit.potion.PotionEffectType.getById(j), this.containerAccess.getLocation().getBlock()); ++ if (event.callEvent()) { ++ this.containerProperties.setProperty(1, event.getPrimary() == null ? 0 : event.getPrimary().getId()); ++ this.containerProperties.setProperty(2, event.getSecondary() == null ? 0 : event.getSecondary().getId()); ++ if (!event.willConsumeItem()) return; + this.d.a(1); ++ } ++ // Paper end + } + + } diff --git a/patches/server-unmapped/0001/0660-Fix-console-spam-when-removing-chests-in-water.patch b/patches/server-unmapped/0001/0660-Fix-console-spam-when-removing-chests-in-water.patch new file mode 100644 index 0000000000..d05dbe7aff --- /dev/null +++ b/patches/server-unmapped/0001/0660-Fix-console-spam-when-removing-chests-in-water.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Thu, 19 Nov 2020 02:07:10 +0000 +Subject: [PATCH] Fix console spam when removing chests in water + + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockChest.java b/src/main/java/net/minecraft/world/level/block/BlockChest.java +index b2c29cff5883868cb56a4e376ab946ac929abc94..a45ee959f41e7f349ff2c309f21fa44ec671cb87 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockChest.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockChest.java +@@ -239,7 +239,7 @@ public class BlockChest extends BlockChestAbstract implements I + @Override + public void remove(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { + if (!iblockdata.a(iblockdata1.getBlock())) { +- TileEntity tileentity = world.getTileEntity(blockposition); ++ TileEntity tileentity = world.getTileEntity(blockposition, false); // Paper - Don't validate TE - Fix console spam when removing chests in water + + if (tileentity instanceof IInventory) { + InventoryUtils.dropInventory(world, blockposition, (IInventory) tileentity); diff --git a/patches/server-unmapped/0001/0661-Add-toggle-for-always-placing-the-dragon-egg.patch b/patches/server-unmapped/0001/0661-Add-toggle-for-always-placing-the-dragon-egg.patch new file mode 100644 index 0000000000..4b1f0d6201 --- /dev/null +++ b/patches/server-unmapped/0001/0661-Add-toggle-for-always-placing-the-dragon-egg.patch @@ -0,0 +1,33 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 849603de7c918788f1f80d7effb93658a69b0fd6..1ceacb6bbfe99069763845a8aef48a3fb4841e32 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -794,4 +794,9 @@ public class PaperWorldConfig { + if (value != -1) entityPerChunkSaveLimits.put(type, value); + }); + } ++ ++ public boolean enderDragonsDeathAlwaysPlacesDragonEgg = false; ++ private void enderDragonsDeathAlwaysPlacesDragonEgg() { ++ enderDragonsDeathAlwaysPlacesDragonEgg = getBoolean("ender-dragons-death-always-places-dragon-egg", enderDragonsDeathAlwaysPlacesDragonEgg); ++ } + } +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +index a255675375d0e50a45694f09056a98dbd7449ecd..546a248370b864b3c1822f3cd3190d50d06e3eb6 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +@@ -400,7 +400,7 @@ public class EnderDragonBattle { + this.bossBattle.setVisible(false); + this.generateExitPortal(true); + this.n(); +- if (!this.previouslyKilled) { ++ if (this.world.paperConfig.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - always place dragon egg + this.world.setTypeUpdate(this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, WorldGenEndTrophy.a), Blocks.DRAGON_EGG.getBlockData()); + } + diff --git a/patches/server-unmapped/0001/0662-Added-PlayerStonecutterRecipeSelectEvent.patch b/patches/server-unmapped/0001/0662-Added-PlayerStonecutterRecipeSelectEvent.patch new file mode 100644 index 0000000000..6444ee5cbe --- /dev/null +++ b/patches/server-unmapped/0001/0662-Added-PlayerStonecutterRecipeSelectEvent.patch @@ -0,0 +1,109 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 27 Nov 2020 17:14:27 -0800 +Subject: [PATCH] Added PlayerStonecutterRecipeSelectEvent + +Co-Authored-By: MiniDigger + +diff --git a/src/main/java/net/minecraft/world/inventory/Container.java b/src/main/java/net/minecraft/world/inventory/Container.java +index bdd00608a72dd81003731ff5fbe774dfdc5220e5..b58ec4abff2840556eb06e08b241a2eaa85c2c7f 100644 +--- a/src/main/java/net/minecraft/world/inventory/Container.java ++++ b/src/main/java/net/minecraft/world/inventory/Container.java +@@ -118,7 +118,7 @@ public abstract class Container { + return slot; + } + +- protected ContainerProperty a(ContainerProperty containerproperty) { ++ protected ContainerProperty addDataSlot(ContainerProperty containerproperty) { return a(containerproperty); } protected ContainerProperty a(ContainerProperty containerproperty) { // Paper - OBFHELPER + this.d.add(containerproperty); + return containerproperty; + } +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerProperty.java b/src/main/java/net/minecraft/world/inventory/ContainerProperty.java +index 67c3b7ddb0b0f10c82577cbea7506c9d80d41368..b9c82e68a811c2ef6bf2d5ce3ae9f858bcb263d5 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerProperty.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerProperty.java +@@ -20,7 +20,7 @@ public abstract class ContainerProperty { + }; + } + +- public static ContainerProperty a(final int[] aint, final int i) { ++ public static ContainerProperty shared(final int[] aint, final int i) { return a(aint, i); } public static ContainerProperty a(final int[] aint, final int i) { // Paper - OBFHELPER + return new ContainerProperty() { + @Override + public int get() { +@@ -54,7 +54,7 @@ public abstract class ContainerProperty { + + public abstract void set(int i); + +- public boolean c() { ++ public boolean checkAndClearUpdateFlag() { return c(); } public boolean c() { // Paper - OBFHELPER + int i = this.get(); + boolean flag = i != this.a; + +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerStonecutter.java b/src/main/java/net/minecraft/world/inventory/ContainerStonecutter.java +index 1589d9ca201d386d11d9fd57fa8ba6848bae215c..dc667dd5d8514ae4c8a2087cb913f44320ebfc48 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerStonecutter.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerStonecutter.java +@@ -22,13 +22,14 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryStonecutter; + import org.bukkit.craftbukkit.inventory.CraftInventoryView; + import org.bukkit.entity.Player; + // CraftBukkit end ++import io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent; // Paper + + public class ContainerStonecutter extends Container { + + private final ContainerAccess containerAccess; + private final ContainerProperty containerProperty; + private final World world; +- private List i; ++ private List i; public final List getRecipes() { return this.i; } // Paper - OBFHELPER + private ItemStack j; + private long k; + final Slot c; +@@ -58,7 +59,7 @@ public class ContainerStonecutter extends Container { + + public ContainerStonecutter(int i, PlayerInventory playerinventory, final ContainerAccess containeraccess) { + super(Containers.STONECUTTER, i); +- this.containerProperty = ContainerProperty.a(); ++ this.containerProperty = addDataSlot(ContainerProperty.shared(new int[1], 0)); // Paper - allow replication + this.i = Lists.newArrayList(); + this.j = ItemStack.b; + this.l = () -> { +@@ -136,13 +137,36 @@ public class ContainerStonecutter extends Container { + @Override + public boolean a(EntityHuman entityhuman, int i) { + if (this.d(i)) { +- this.containerProperty.set(i); ++ // Paper start ++ int recipeIndex = i; ++ this.containerProperty.set(recipeIndex); ++ this.containerProperty.checkAndClearUpdateFlag(); // mark as changed ++ if (this.isValidRecipeIndex(i)) { ++ PlayerStonecutterRecipeSelectEvent event = new PlayerStonecutterRecipeSelectEvent((Player) entityhuman.getBukkitEntity(), (org.bukkit.inventory.StonecutterInventory) getBukkitView().getTopInventory(), (org.bukkit.inventory.StonecuttingRecipe) this.getRecipes().get(i).toBukkitRecipe()); ++ if (!event.callEvent()) { ++ ((Player) entityhuman.getBukkitEntity()).updateInventory(); ++ return false; ++ } ++ int newRecipeIndex; ++ if (!this.getRecipes().get(recipeIndex).getKey().equals(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey()))) { // If the recipe did NOT stay the same ++ for (newRecipeIndex = 0; newRecipeIndex < this.getRecipes().size(); newRecipeIndex++) { ++ if (this.getRecipes().get(newRecipeIndex).getKey().equals(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey()))) { ++ recipeIndex = newRecipeIndex; ++ break; ++ } ++ } ++ } ++ } ++ ((Player) entityhuman.getBukkitEntity()).updateInventory(); ++ this.containerProperty.set(recipeIndex); // set new index, so that listeners can read it ++ // Paper end + this.i(); + } + + return true; + } + ++ private boolean isValidRecipeIndex(int index) { return this.d(index); } // Paper - OBFHELPER + private boolean d(int i) { + return i >= 0 && i < this.i.size(); + } diff --git a/patches/server-unmapped/0001/0663-Add-dropLeash-variable-to-EntityUnleashEvent.patch b/patches/server-unmapped/0001/0663-Add-dropLeash-variable-to-EntityUnleashEvent.patch new file mode 100644 index 0000000000..29161b0711 --- /dev/null +++ b/patches/server-unmapped/0001/0663-Add-dropLeash-variable-to-EntityUnleashEvent.patch @@ -0,0 +1,156 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KennyTV +Date: Fri, 29 Jan 2021 15:13:11 +0100 +Subject: [PATCH] Add dropLeash variable to EntityUnleashEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityCreature.java b/src/main/java/net/minecraft/world/entity/EntityCreature.java +index 20570580367697e37e6c45147168c3beb6c8d31b..831414980d40f4382cf7370db28dd8fae534384b 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityCreature.java ++++ b/src/main/java/net/minecraft/world/entity/EntityCreature.java +@@ -48,8 +48,11 @@ public abstract class EntityCreature extends EntityInsentient { + + if (this instanceof EntityTameableAnimal && ((EntityTameableAnimal) this).isSitting()) { + if (f > entity.world.paperConfig.maxLeashDistance) { // Paper +- this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit +- this.unleash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ this.world.getServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.unleash(true, event.isDropLeash()); ++ // Paper end + } + + return; +@@ -57,8 +60,11 @@ public abstract class EntityCreature extends EntityInsentient { + + this.x(f); + if (f > entity.world.paperConfig.maxLeashDistance) { // Paper +- this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit +- this.unleash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ this.world.getServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.unleash(true, event.isDropLeash()); ++ // Paper end + this.goalSelector.a(PathfinderGoal.Type.MOVE); + } else if (f > 6.0F) { + double d0 = (entity.locX() - this.locX()) / (double) f; +diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +index ff482e3774f580d8ba7028f6c5141888d3bd907a..a246edd09854dabf095da75c9d200f5cf26e7138 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java ++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java +@@ -88,6 +88,7 @@ import org.bukkit.event.entity.EntityTargetEvent; + import org.bukkit.event.entity.EntityTransformEvent; + import org.bukkit.event.entity.EntityUnleashEvent; + import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; ++import org.bukkit.event.player.PlayerUnleashEntityEvent; // Paper + // CraftBukkit end + + public abstract class EntityInsentient extends EntityLiving { +@@ -1207,12 +1208,15 @@ public abstract class EntityInsentient extends EntityLiving { + return EnumInteractionResult.PASS; + } else if (this.getLeashHolder() == entityhuman) { + // CraftBukkit start - fire PlayerUnleashEntityEvent +- if (CraftEventFactory.callPlayerUnleashEntityEvent(this, entityhuman).isCancelled()) { ++ // Paper start - drop leash variable ++ PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, entityhuman, !entityhuman.abilities.canInstantlyBuild); ++ if (event.isCancelled()) { ++ // Paper end + ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutAttachEntity(this, this.getLeashHolder())); + return EnumInteractionResult.PASS; + } + // CraftBukkit end +- this.unleash(true, !entityhuman.abilities.canInstantlyBuild); ++ this.unleash(true, event.isDropLeash()); // Paper - drop leash variable + return EnumInteractionResult.a(this.world.isClientSide); + } else { + EnumInteractionResult enuminteractionresult = this.c(entityhuman, enumhand); +@@ -1366,8 +1370,11 @@ public abstract class EntityInsentient extends EntityLiving { + + if (this.leashHolder != null) { + if (!this.isAlive() || !this.leashHolder.isAlive()) { +- this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE)); // CraftBukkit +- this.unleash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE, true); ++ this.world.getServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.unleash(true, event.isDropLeash()); ++ // Paper end + } + + } +@@ -1435,8 +1442,11 @@ public abstract class EntityInsentient extends EntityLiving { + boolean flag1 = super.a(entity, flag); + + if (flag1 && this.isLeashed()) { +- this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit +- this.unleash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, true); ++ this.world.getServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.unleash(true, event.isDropLeash()); ++ // Paper end + } + + return flag1; +@@ -1638,7 +1648,10 @@ public abstract class EntityInsentient extends EntityLiving { + @Override + protected void bN() { + super.bN(); +- this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit +- this.unleash(true, false); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, false); ++ this.world.getServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.unleash(true, event.isDropLeash()); ++ // Paper end + } + } +diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityLeash.java b/src/main/java/net/minecraft/world/entity/decoration/EntityLeash.java +index 8f6d2a6a388021f437ac5554e9ece8eca89e1f46..519f0cabadcf97a44a112fd963a8d3ab194650c4 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/EntityLeash.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityLeash.java +@@ -30,6 +30,8 @@ import net.minecraft.server.level.WorldServer; + import org.bukkit.craftbukkit.event.CraftEventFactory; + // CraftBukkit end + ++import org.bukkit.event.player.PlayerUnleashEntityEvent; // Paper ++ + public class EntityLeash extends EntityHanging { + + public EntityLeash(EntityTypes entitytypes, World world) { +@@ -125,11 +127,14 @@ public class EntityLeash extends EntityHanging { + entityinsentient = (EntityInsentient) iterator.next(); + if (entityinsentient.isLeashed() && entityinsentient.getLeashHolder() == this) { + // CraftBukkit start +- if (CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient, entityhuman).isCancelled()) { ++ // Paper start - drop leash variable ++ PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient, entityhuman, !entityhuman.abilities.canInstantlyBuild); ++ if (event.isCancelled()) { ++ // Paper end + die = false; + continue; + } +- entityinsentient.unleash(true, !entityhuman.abilities.canInstantlyBuild); // false -> survival mode boolean ++ entityinsentient.unleash(true, event.isDropLeash()); // false -> survival mode boolean // Paper - drop leash variable + // 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 47906539c3e6cd7f34c0880a0bab2a185d79b71c..32fd193d72521525972445199e02c2ae7a0e771a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1450,8 +1450,10 @@ public class CraftEventFactory { + return itemInHand; + } + +- public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(EntityInsentient entity, EntityHuman player) { +- PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity()); ++ // Paper start - drop leash variable ++ public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(EntityInsentient entity, EntityHuman player, boolean dropLeash) { ++ PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), dropLeash); ++ // Paper end + entity.world.getServer().getPluginManager().callEvent(event); + return event; + } diff --git a/patches/server-unmapped/0001/0664-Skip-distance-map-update-when-spawning-disabled.patch b/patches/server-unmapped/0001/0664-Skip-distance-map-update-when-spawning-disabled.patch new file mode 100644 index 0000000000..c6dcc7a20e --- /dev/null +++ b/patches/server-unmapped/0001/0664-Skip-distance-map-update-when-spawning-disabled.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Beech Horn +Date: Fri, 14 Feb 2020 19:39:59 +0000 +Subject: [PATCH] Skip distance map update when spawning disabled. + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +index e456ac2c1d57dcf6ad7dffb7f1ed45be7411dedb..8de9702f3effd0a94218bfd26f63cf40f184c3b7 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java +@@ -826,7 +826,7 @@ public class ChunkProviderServer extends IChunkProvider { + int l = this.chunkMapDistance.b(); + // Paper start - per player mob spawning + SpawnerCreature.d spawnercreature_d; // moved down +- if (this.playerChunkMap.playerMobDistanceMap != null) { ++ if ((this.allowAnimals || this.allowMonsters) && this.playerChunkMap.playerMobDistanceMap != null) { // don't update when animals and monsters are disabled + // update distance map + this.world.timings.playerMobDistanceMapUpdate.startTiming(); + this.playerChunkMap.playerMobDistanceMap.update(this.world.players, this.playerChunkMap.viewDistance); diff --git a/patches/server-unmapped/0001/0665-Reset-shield-blocking-on-dimension-change.patch b/patches/server-unmapped/0001/0665-Reset-shield-blocking-on-dimension-change.patch new file mode 100644 index 0000000000..878a2e6a56 --- /dev/null +++ b/patches/server-unmapped/0001/0665-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/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java +index 45c6eb96310146adab802dc3da019f7ee15e0fe5..1161605d9f4f9727282ac3677a916a9ebdb1263b 100644 +--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java +@@ -1117,6 +1117,11 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.world.getServer().getPluginManager().callEvent(changeEvent); + // CraftBukkit end + } ++ // Paper start ++ if (this.isBlocking()) { ++ this.clearActiveItem(); ++ } ++ // Paper end + + return this; + } diff --git a/patches/server-unmapped/0001/0666-add-DragonEggFormEvent.patch b/patches/server-unmapped/0001/0666-add-DragonEggFormEvent.patch new file mode 100644 index 0000000000..c615356f95 --- /dev/null +++ b/patches/server-unmapped/0001/0666-add-DragonEggFormEvent.patch @@ -0,0 +1,66 @@ +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/EnderDragonBattle.java b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +index 546a248370b864b3c1822f3cd3190d50d06e3eb6..43a9f17de7229a415daa0f9256a1353f6820e824 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EnderDragonBattle.java +@@ -57,6 +57,7 @@ import net.minecraft.world.level.levelgen.feature.configurations.WorldGenFeature + import net.minecraft.world.phys.AxisAlignedBB; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import io.papermc.paper.event.block.DragonEggFormEvent; // Paper - DragonEggFormEvent + + public class EnderDragonBattle { + +@@ -400,9 +401,24 @@ public class EnderDragonBattle { + this.bossBattle.setVisible(false); + this.generateExitPortal(true); + this.n(); ++ // Paper start - DragonEggFormEvent ++ BlockPosition eggPosition = this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, WorldGenEndTrophy.getPosition()); ++ org.bukkit.craftbukkit.block.CraftBlock eggBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this.world, eggPosition); ++ org.bukkit.craftbukkit.block.CraftBlockState eggState = new org.bukkit.craftbukkit.block.CraftBlockState(eggBlock); ++ eggState.setData(Blocks.DRAGON_EGG.getBlockData()); ++ DragonEggFormEvent eggEvent = new DragonEggFormEvent(eggBlock, eggState, ++ new org.bukkit.craftbukkit.boss.CraftDragonBattle(this)); ++ // Paper end - DragonEggFormEvent + if (this.world.paperConfig.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - always place dragon egg +- this.world.setTypeUpdate(this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, WorldGenEndTrophy.a), Blocks.DRAGON_EGG.getBlockData()); ++ // Paper start - DragonEggFormEvent ++ //this.world.setTypeUpdate(this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, WorldGenEndTrophy.a), Blocks.DRAGON_EGG.getBlockData()); ++ } else { ++ eggEvent.setCancelled(true); ++ } ++ if (eggEvent.callEvent()) { ++ eggEvent.getNewState().update(true); + } ++ // Paper end - DragonEggFormEvent + + this.previouslyKilled = true; + this.dragonKilled = true; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/WorldGenEndTrophy.java b/src/main/java/net/minecraft/world/level/levelgen/feature/WorldGenEndTrophy.java +index 18395a3b4a7df1c99e952b9c8e738f165648eba5..851b8c2a86d7155278b49c44c6db8b1cbd2065f2 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/WorldGenEndTrophy.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/WorldGenEndTrophy.java +@@ -14,7 +14,7 @@ import net.minecraft.world.level.levelgen.feature.configurations.WorldGenFeature + + public class WorldGenEndTrophy extends WorldGenerator { + +- public static final BlockPosition a = BlockPosition.ZERO; ++ public static final BlockPosition a = BlockPosition.ZERO; public static BlockPosition getPosition() { return a; } // Paper - OBFHELPER + private final boolean ab; + + public WorldGenEndTrophy(boolean flag) { +@@ -22,7 +22,7 @@ public class WorldGenEndTrophy extends WorldGenerator +Date: Tue, 11 Feb 2020 21:56:48 -0600 +Subject: [PATCH] EntityMoveEvent + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 0ff63eede271b555e9de9b61dc76045d450cd990..697ce13c7c32e4badcd171c1e9eefc49620ae525 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -12,6 +12,7 @@ import com.mojang.datafixers.DataFixer; + import io.netty.buffer.ByteBuf; + import io.netty.buffer.ByteBufOutputStream; + import io.netty.buffer.Unpooled; ++import io.papermc.paper.event.entity.EntityMoveEvent; + import it.unimi.dsi.fastutil.longs.LongIterator; + import java.awt.image.BufferedImage; + import java.io.BufferedWriter; +@@ -1458,6 +1459,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0; // Paper ++ worldserver.hasEntityMoveEvent = EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper + TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + + this.methodProfiler.a(() -> { +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index c38ef337f9a662d689994a0d530e8e655b843177..bab75b2232f1fa1def09517610179ca1529d195e 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -213,6 +213,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + public final Convertable.ConversionSession convertable; + public final UUID uuid; + public boolean hasPhysicsEvent = true; // Paper ++ public boolean hasEntityMoveEvent = false; // Paper + private static Throwable getAddToWorldStackTrace(Entity entity) { + return new Throwable(entity + " Added to world at " + new java.util.Date()); + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index deffd82dfca1d2eea6e5b8db9228015bf35ad0a3..21341eeb8148be119fbc1dd370c1beaf70a319e0 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -10,6 +10,7 @@ import com.mojang.datafixers.util.Pair; + import com.mojang.serialization.DataResult; + import com.mojang.serialization.Dynamic; + import com.mojang.serialization.DynamicOps; ++import io.papermc.paper.event.entity.EntityMoveEvent; + import java.util.Collection; + import java.util.ConcurrentModificationException; + import java.util.Iterator; +@@ -2912,6 +2913,20 @@ public abstract class EntityLiving extends Entity { + + this.collideNearby(); + this.world.getMethodProfiler().exit(); ++ // Paper start ++ if (((WorldServer) world).hasEntityMoveEvent) { ++ if (lastX != locX() || lastY != locY() || lastZ != locZ() || lastYaw != yaw || lastPitch != pitch) { ++ Location from = new Location(world.getWorld(), lastX, lastY, lastZ, lastYaw, lastPitch); ++ Location to = new Location (world.getWorld(), locX(), locY(), locZ(), yaw, pitch); ++ EntityMoveEvent event = new EntityMoveEvent(getBukkitLivingEntity(), from, to.clone()); ++ if (!event.callEvent()) { ++ setLocation(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); ++ } else if (!to.equals(event.getTo())) { ++ setLocation(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); ++ } ++ } ++ } ++ // Paper end + if (!this.world.isClientSide && this.dO() && this.aG()) { + this.damageEntity(DamageSource.DROWN, 1.0F); + } diff --git a/patches/server-unmapped/0001/0668-added-option-to-disable-pathfinding-updates-on-block.patch b/patches/server-unmapped/0001/0668-added-option-to-disable-pathfinding-updates-on-block.patch new file mode 100644 index 0000000000..998ea4ba36 --- /dev/null +++ b/patches/server-unmapped/0001/0668-added-option-to-disable-pathfinding-updates-on-block.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lukas81298 +Date: Mon, 25 Jan 2021 14:37:57 +0100 +Subject: [PATCH] added option to disable pathfinding updates on block changes + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 1ceacb6bbfe99069763845a8aef48a3fb4841e32..d2f6e1308a4dfec663770e2c7f4de0cf22402a97 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -799,4 +799,9 @@ public class PaperWorldConfig { + private void enderDragonsDeathAlwaysPlacesDragonEgg() { + enderDragonsDeathAlwaysPlacesDragonEgg = getBoolean("ender-dragons-death-always-places-dragon-egg", enderDragonsDeathAlwaysPlacesDragonEgg); + } ++ ++ public boolean updatePathfindingOnBlockUpdate = true; ++ private void setUpdatePathfindingOnBlockUpdate() { ++ updatePathfindingOnBlockUpdate = getBoolean("update-pathfinding-on-block-update", this.updatePathfindingOnBlockUpdate); ++ } + } +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index bab75b2232f1fa1def09517610179ca1529d195e..bea183ba796f2acf5465ad91e4e7fe3e73c9da74 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1685,6 +1685,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + @Override + public void notify(BlockPosition blockposition, IBlockData iblockdata, IBlockData iblockdata1, int i) { + this.getChunkProvider().flagDirty(blockposition); ++ if(this.paperConfig.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates + VoxelShape voxelshape = iblockdata.getCollisionShape(this, blockposition); + VoxelShape voxelshape1 = iblockdata1.getCollisionShape(this, blockposition); + +@@ -1713,6 +1714,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + this.tickingEntities = wasTicking; // Paper + } ++ } // Paper + } + + @Override diff --git a/patches/server-unmapped/0001/0669-Inline-shift-direction-fields.patch b/patches/server-unmapped/0001/0669-Inline-shift-direction-fields.patch new file mode 100644 index 0000000000..e044ac5972 --- /dev/null +++ b/patches/server-unmapped/0001/0669-Inline-shift-direction-fields.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 18 Jan 2021 20:45:25 -0500 +Subject: [PATCH] Inline shift direction fields + +Removes a layer of indirection for EnumDirection.getAdjacent(X|Y|Z)(), which is in the +critical section for much of the server, including the lighting engine. + +diff --git a/src/main/java/net/minecraft/core/EnumDirection.java b/src/main/java/net/minecraft/core/EnumDirection.java +index a699005582293326076eaa80655c5343e6c22ff0..703bdefeb615ef8d15b428a893b5e4939d726f13 100644 +--- a/src/main/java/net/minecraft/core/EnumDirection.java ++++ b/src/main/java/net/minecraft/core/EnumDirection.java +@@ -53,6 +53,11 @@ public enum EnumDirection implements INamable { + }, (enumdirection, enumdirection1) -> { + throw new IllegalArgumentException("Duplicate keys"); + }, Long2ObjectOpenHashMap::new)); ++ // Paper start ++ private final int adjX; ++ private final int adjY; ++ private final int adjZ; ++ // Paper end + + private EnumDirection(int i, int j, int k, String s, EnumDirection.EnumAxisDirection enumdirection_enumaxisdirection, EnumDirection.EnumAxis enumdirection_enumaxis, BaseBlockPosition baseblockposition) { + this.g = i; +@@ -62,6 +67,11 @@ public enum EnumDirection implements INamable { + this.k = enumdirection_enumaxis; + this.l = enumdirection_enumaxisdirection; + this.m = baseblockposition; ++ // Paper start ++ this.adjX = baseblockposition.getX(); ++ this.adjY = baseblockposition.getY(); ++ this.adjZ = baseblockposition.getZ(); ++ // Paper end + } + + public static EnumDirection[] a(Entity entity) { +@@ -137,15 +147,15 @@ public enum EnumDirection implements INamable { + } + + public int getAdjacentX() { +- return this.m.getX(); ++ return this.adjX; // Paper + } + + public int getAdjacentY() { +- return this.m.getY(); ++ return this.adjY; // Paper + } + + public int getAdjacentZ() { +- return this.m.getZ(); ++ return this.adjZ; // Paper + } + + public String m() { diff --git a/patches/server-unmapped/0001/0670-Allow-adding-items-to-BlockDropItemEvent.patch b/patches/server-unmapped/0001/0670-Allow-adding-items-to-BlockDropItemEvent.patch new file mode 100644 index 0000000000..53409ac8fa --- /dev/null +++ b/patches/server-unmapped/0001/0670-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 32fd193d72521525972445199e02c2ae7a0e771a..9084aa4b7c0059c995a3d1a89188379b52c9d620 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -383,13 +383,30 @@ public class CraftEventFactory { + } + + public static void handleBlockDropItemEvent(Block block, BlockState state, EntityPlayer player, List items) { +- BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), Lists.transform(items, (item) -> (org.bukkit.entity.Item) item.getBukkitEntity())); ++ // Paper start ++ List list = new ArrayList<>(); ++ for (EntityItem item : items) { ++ list.add((Item) item.getBukkitEntity()); ++ } ++ BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), list); ++ // Paper end + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +- for (EntityItem item : items) { +- item.world.addEntity(item); ++ // Paper start ++ for (Item bukkit : list) { ++ if (!bukkit.isValid()) { ++ Entity item = ((org.bukkit.craftbukkit.entity.CraftItem) bukkit).getHandle(); ++ item.world.addEntity(item); ++ } ++ } ++ } else { ++ for (Item bukkit : list) { ++ if (bukkit.isValid()) { ++ bukkit.remove(); ++ } + } ++ // Paper end + } + } + diff --git a/patches/server-unmapped/0001/0671-Add-getMainThreadExecutor-to-BukkitScheduler.patch b/patches/server-unmapped/0001/0671-Add-getMainThreadExecutor-to-BukkitScheduler.patch new file mode 100644 index 0000000000..f2acd9e212 --- /dev/null +++ b/patches/server-unmapped/0001/0671-Add-getMainThreadExecutor-to-BukkitScheduler.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aleksander Jagiello +Date: Sun, 24 Jan 2021 22:17:54 +0100 +Subject: [PATCH] Add getMainThreadExecutor to BukkitScheduler + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 13e461ffb2ee2e7d0440c0f60809ea99629b843c..0be39dac4b9dd69d7d73d86d64cf1e33e4086e81 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -635,4 +635,15 @@ public class CraftScheduler implements BukkitScheduler { + public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException { + throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimerAsynchronously(Plugin, long, long)"); + } ++ ++ // Paper start - add getMainThreadExecutor ++ @Override ++ public Executor getMainThreadExecutor(Plugin plugin) { ++ Validate.notNull(plugin, "Plugin cannot be null"); ++ return command -> { ++ Validate.notNull(command, "Command cannot be null"); ++ this.runTask(plugin, command); ++ }; ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0672-living-entity-allow-attribute-registration.patch b/patches/server-unmapped/0001/0672-living-entity-allow-attribute-registration.patch new file mode 100644 index 0000000000..73c28af9be --- /dev/null +++ b/patches/server-unmapped/0001/0672-living-entity-allow-attribute-registration.patch @@ -0,0 +1,69 @@ +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/AttributeMapBase.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMapBase.java +index 8f6b78c68da555f96033df567da581af52195e6c..e4cd1848e8700de4ab64f3037bb0c41d99e7c97d 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMapBase.java ++++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMapBase.java +@@ -21,7 +21,7 @@ import org.apache.logging.log4j.Logger; + public class AttributeMapBase { + + private static final Logger LOGGER = LogManager.getLogger(); +- private final Map b = Maps.newHashMap(); ++ private final Map b = Maps.newHashMap(); private final Map attributeMap = b; // Paper - OBFHELPER + private final Set c = Sets.newHashSet(); + private final AttributeProvider d; + +@@ -135,4 +135,12 @@ public class AttributeMapBase { + } + + } ++ ++ // Paper - start ++ public void registerAttribute(AttributeBase attributeBase) { ++ AttributeModifiable attributeModifiable = new AttributeModifiable(attributeBase, AttributeModifiable::getAttribute); ++ attributeMap.put(attributeBase, attributeModifiable); ++ } ++ // Paper - end ++ + } +diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +index 673948947bd918c1dbb6c4c99486b4200e3c09fe..2e83b8855070077e90e5ab2c4beae819c620e480 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +@@ -40,6 +40,14 @@ public class CraftAttributeMap implements Attributable { + return (nms == null) ? null : new CraftAttributeInstance(nms, attribute); + } + ++ // Paper start ++ @Override ++ public void registerAttribute(Attribute attribute) { ++ Preconditions.checkArgument(attribute != null, "attribute"); ++ handle.registerAttribute(CraftAttributeMap.toMinecraft(attribute)); ++ } ++ // Paper end ++ + public static AttributeBase toMinecraft(Attribute attribute) { + return IRegistry.ATTRIBUTE.get(CraftNamespacedKey.toMinecraft(attribute.getKey())); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index e574e2453c7bc848168ff24372d6772bd423b672..3d497f69f89455b88fba423de8effb3db83e7af4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -675,6 +675,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return getHandle().craftAttributes.getAttribute(attribute); + } + ++ // Paper start ++ @Override ++ public void registerAttribute(Attribute attribute) { ++ getHandle().craftAttributes.registerAttribute(attribute); ++ } ++ // Paper end ++ + @Override + public void setAI(boolean ai) { + if (this.getHandle() instanceof EntityInsentient) { diff --git a/patches/server-unmapped/0001/0673-fix-dead-slime-setSize-invincibility.patch b/patches/server-unmapped/0001/0673-fix-dead-slime-setSize-invincibility.patch new file mode 100644 index 0000000000..33f5d17581 --- /dev/null +++ b/patches/server-unmapped/0001/0673-fix-dead-slime-setSize-invincibility.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Fri, 5 Feb 2021 22:12:13 +0100 +Subject: [PATCH] fix dead slime setSize invincibility + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +index cf5c6030105e56813f526e710e5db0c59d88c99e..13ab1a430f9ad2ece73ab50455bfcddbc9b236e2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +@@ -18,7 +18,7 @@ public class CraftSlime extends CraftMob implements Slime { + + @Override + public void setSize(int size) { +- getHandle().setSize(size, true); ++ getHandle().setSize(size, /* true */ getHandle().isAlive()); // Paper - fix dead slime setSize invincibility + } + + @Override diff --git a/patches/server-unmapped/0001/0674-Merchant-getRecipes-should-return-an-immutable-list.patch b/patches/server-unmapped/0001/0674-Merchant-getRecipes-should-return-an-immutable-list.patch new file mode 100644 index 0000000000..f368eba156 --- /dev/null +++ b/patches/server-unmapped/0001/0674-Merchant-getRecipes-should-return-an-immutable-list.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Wed, 10 Feb 2021 14:53:36 -0800 +Subject: [PATCH] Merchant#getRecipes should return an immutable list + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java +index 00d43e4a77ed03bc9672cbaccad50b48aed0fb93..3c66d9217eb0b60595a59e2f296de332095d4a4c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java +@@ -25,7 +25,7 @@ public class CraftMerchant implements Merchant { + + @Override + public List getRecipes() { +- return Collections.unmodifiableList(Lists.transform(merchant.getOffers(), new Function() { ++ return com.google.common.collect.ImmutableList.copyOf(Lists.transform(merchant.getOffers(), new Function() { // Paper - javadoc says 'an immutable list of trades' - not 'an unmodifiable view of a list of trades'. fixes issue with setRecipes(getRecipes()) + @Override + public MerchantRecipe apply(net.minecraft.world.item.trading.MerchantRecipe recipe) { + return recipe.asBukkit(); diff --git a/patches/server-unmapped/0001/0675-misc-debugging-dumps.patch b/patches/server-unmapped/0001/0675-misc-debugging-dumps.patch new file mode 100644 index 0000000000..02b602485b --- /dev/null +++ b/patches/server-unmapped/0001/0675-misc-debugging-dumps.patch @@ -0,0 +1,87 @@ +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/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 697ce13c7c32e4badcd171c1e9eefc49620ae525..84c3110ea03f9121fc4ab0aaa80ddad5efe28e5c 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -13,6 +13,7 @@ import io.netty.buffer.ByteBuf; + import io.netty.buffer.ByteBufOutputStream; + import io.netty.buffer.Unpooled; + import io.papermc.paper.event.entity.EntityMoveEvent; ++import io.papermc.paper.util.TraceUtil; + import it.unimi.dsi.fastutil.longs.LongIterator; + import java.awt.image.BufferedImage; + import java.io.BufferedWriter; +@@ -855,6 +856,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Sat, 20 Feb 2021 13:09:59 -0500 +Subject: [PATCH] Add support for hex color codes in console + +Converts upstream's hex color code legacy format into actual hex color codes in the console. + +diff --git a/src/main/java/io/papermc/paper/console/HexFormattingConverter.java b/src/main/java/io/papermc/paper/console/HexFormattingConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a4315961b7a465fb4872a4d67e7c26d4b4ed1fb9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/HexFormattingConverter.java +@@ -0,0 +1,178 @@ ++package io.papermc.paper.console; ++ ++import net.minecrell.terminalconsole.TerminalConsoleAppender; ++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.*; ++import org.apache.logging.log4j.util.PerformanceSensitive; ++import org.apache.logging.log4j.util.PropertiesUtil; ++ ++import java.util.List; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++import static net.minecrell.terminalconsole.MinecraftFormattingConverter.KEEP_FORMATTING_PROPERTY; ++ ++/** ++ * Modified version of ++ * TerminalConsoleAppender's MinecraftFormattingConverter to support hex color codes using the md_5 &x&r&r&g&g&b&b format. ++ */ ++@Plugin(name = "paperMinecraftFormatting", category = PatternConverter.CATEGORY) ++@ConverterKeys({ "paperMinecraftFormatting" }) ++@PerformanceSensitive("allocation") ++public final class HexFormattingConverter extends LogEventPatternConverter { ++ ++ private static final boolean KEEP_FORMATTING = PropertiesUtil.getProperties().getBooleanProperty(KEEP_FORMATTING_PROPERTY); ++ ++ private static final String ANSI_RESET = "\u001B[m"; ++ ++ private static final char COLOR_CHAR = '§'; ++ private static final String LOOKUP = "0123456789abcdefklmnor"; ++ ++ private static final String RGB_ANSI = "\u001B[38;2;%d;%d;%dm"; ++ private static final Pattern NAMED_PATTERN = Pattern.compile(COLOR_CHAR + "[0-9a-fk-orA-FK-OR]"); ++ private static final Pattern RGB_PATTERN = Pattern.compile(COLOR_CHAR + "x(" + COLOR_CHAR + "[0-9a-fA-F]){6}"); ++ ++ private static final String[] ansiCodes = new String[] { ++ "\u001B[0;30m", // Black §0 ++ "\u001B[0;34m", // Dark Blue §1 ++ "\u001B[0;32m", // Dark Green §2 ++ "\u001B[0;36m", // Dark Aqua §3 ++ "\u001B[0;31m", // Dark Red §4 ++ "\u001B[0;35m", // Dark Purple §5 ++ "\u001B[0;33m", // Gold §6 ++ "\u001B[0;37m", // Gray §7 ++ "\u001B[0;30;1m", // Dark Gray §8 ++ "\u001B[0;34;1m", // Blue §9 ++ "\u001B[0;32;1m", // Green §a ++ "\u001B[0;36;1m", // Aqua §b ++ "\u001B[0;31;1m", // Red §c ++ "\u001B[0;35;1m", // Light Purple §d ++ "\u001B[0;33;1m", // Yellow §e ++ "\u001B[0;37;1m", // White §f ++ "\u001B[5m", // Obfuscated §k ++ "\u001B[21m", // Bold §l ++ "\u001B[9m", // Strikethrough §m ++ "\u001B[4m", // Underline §n ++ "\u001B[3m", // Italic §o ++ ANSI_RESET, // Reset §r ++ }; ++ ++ private final boolean ansi; ++ private final List formatters; ++ ++ /** ++ * Construct the converter. ++ * ++ * @param formatters The pattern formatters to generate the text to manipulate ++ * @param strip If true, the converter will strip all formatting codes ++ */ ++ protected HexFormattingConverter(List formatters, boolean strip) { ++ super("paperMinecraftFormatting", null); ++ this.formatters = formatters; ++ this.ansi = !strip; ++ } ++ ++ @Override ++ public void format(LogEvent event, StringBuilder toAppendTo) { ++ int start = toAppendTo.length(); ++ //noinspection ForLoopReplaceableByForEach ++ for (int i = 0, size = formatters.size(); i < size; i++) { ++ formatters.get(i).format(event, toAppendTo); ++ } ++ ++ if (KEEP_FORMATTING || toAppendTo.length() == start) { ++ // Skip replacement if disabled or if the content is empty ++ return; ++ } ++ ++ boolean useAnsi = ansi && TerminalConsoleAppender.isAnsiSupported(); ++ String content = toAppendTo.substring(start); ++ content = useAnsi ? convertRGBColors(content) : stripRGBColors(content); ++ format(content, toAppendTo, start, useAnsi); ++ } ++ ++ private static String convertRGBColors(String input) { ++ Matcher matcher = RGB_PATTERN.matcher(input); ++ StringBuffer buffer = new StringBuffer(); ++ while (matcher.find()) { ++ String s = matcher.group().replace(String.valueOf(COLOR_CHAR), "").replace('x', '#'); ++ int hex = Integer.decode(s); ++ int red = (hex >> 16) & 0xFF; ++ int green = (hex >> 8) & 0xFF; ++ int blue = hex & 0xFF; ++ String replacement = String.format(RGB_ANSI, red, green, blue); ++ matcher.appendReplacement(buffer, replacement); ++ } ++ matcher.appendTail(buffer); ++ return buffer.toString(); ++ } ++ ++ private static String stripRGBColors(String input) { ++ Matcher matcher = RGB_PATTERN.matcher(input); ++ StringBuffer buffer = new StringBuffer(); ++ while (matcher.find()) { ++ matcher.appendReplacement(buffer, ""); ++ } ++ matcher.appendTail(buffer); ++ return buffer.toString(); ++ } ++ ++ static void format(String content, StringBuilder result, int start, boolean ansi) { ++ int next = content.indexOf(COLOR_CHAR); ++ int last = content.length() - 1; ++ if (next == -1 || next == last) { ++ result.setLength(start); ++ result.append(content); ++ if (ansi) { ++ result.append(ANSI_RESET); ++ } ++ return; ++ } ++ ++ Matcher matcher = NAMED_PATTERN.matcher(content); ++ StringBuffer buffer = new StringBuffer(); ++ while (matcher.find()) { ++ int format = LOOKUP.indexOf(Character.toLowerCase(matcher.group().charAt(1))); ++ if (format != -1) { ++ matcher.appendReplacement(buffer, ansi ? ansiCodes[format] : ""); ++ } ++ } ++ matcher.appendTail(buffer); ++ ++ result.setLength(start); ++ result.append(buffer.toString()); ++ if (ansi) { ++ result.append(ANSI_RESET); ++ } ++ } ++ ++ /** ++ * Gets a new instance of the {@link HexFormattingConverter} with the ++ * specified options. ++ * ++ * @param config The current configuration ++ * @param options The pattern options ++ * @return The new instance ++ * ++ * @see HexFormattingConverter ++ */ ++ public static HexFormattingConverter newInstance(Configuration config, String[] options) { ++ if (options.length < 1 || options.length > 2) { ++ LOGGER.error("Incorrect number of options on paperMinecraftFormatting. Expected at least 1, max 2 received " + options.length); ++ return null; ++ } ++ if (options[0] == null) { ++ LOGGER.error("No pattern supplied on paperMinecraftFormatting"); ++ return null; ++ } ++ ++ PatternParser parser = PatternLayout.createPatternParser(config); ++ List formatters = parser.parse(options[0]); ++ boolean strip = options.length > 1 && "strip".equals(options[1]); ++ return new HexFormattingConverter(formatters, strip); ++ } ++ ++} +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 8af159abd3d0cc94cf155fec5b384c42f69551bf..67da1aa7a21622fb231d19dede3775a282a4a12e 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -6,21 +6,21 @@ + + + +- ++ + + + ++ pattern="%highlightError{[%d{HH:mm:ss} %level]: %paperMinecraftFormatting{%msg}%n%xEx{full}}" /> + + + + + +- ++ + + + ++ pattern="[%d{HH:mm:ss}] [%t/%level]: %paperMinecraftFormatting{%msg}{strip}%n%xEx{full}" /> + + + diff --git a/patches/server-unmapped/0001/0677-Clear-SyncLoadInfo.patch b/patches/server-unmapped/0001/0677-Clear-SyncLoadInfo.patch new file mode 100644 index 0000000000..5f1ef784b9 --- /dev/null +++ b/patches/server-unmapped/0001/0677-Clear-SyncLoadInfo.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tom +Date: Fri, 26 Feb 2021 16:10:53 -0600 +Subject: [PATCH] Clear SyncLoadInfo + +This patch merely adds the extra argument "clear" after /paper syncloadinfo to clear currently stored syncload info. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index 4b3efe01750d79bcc27a42b5a145d9aa6b124d18..12313a37ceeb6a0b6a539c38fdba67e5e43d7413 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -295,6 +295,13 @@ public class PaperCommand extends Command { + sender.sendMessage(ChatColor.RED + "This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set."); + return; + } ++ ++ if (args.length > 1 && args[1].equals("clear")) { ++ SyncLoadFinder.clear(); ++ sender.sendMessage(ChatColor.GRAY + "Sync load data cleared."); ++ return; ++ } ++ + File file = new File(new File(new File("."), "debug"), + "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); + file.getParentFile().mkdirs(); +diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +index d381f91cf105bfc01846ada90da8971a3618e784..c51401bcfac0a1e45099af1dd355073c19790476 100644 +--- a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java ++++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +@@ -26,6 +26,10 @@ public class SyncLoadFinder { + public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap(); + } + ++ public static void clear() { ++ SYNC_LOADS.clear(); ++ } ++ + public static void logSyncLoad(final World world, final int chunkX, final int chunkZ) { + if (!ENABLED) { + return; diff --git a/patches/server-unmapped/0001/0678-Expose-Tracked-Players.patch b/patches/server-unmapped/0001/0678-Expose-Tracked-Players.patch new file mode 100644 index 0000000000..cf2ad1c4ed --- /dev/null +++ b/patches/server-unmapped/0001/0678-Expose-Tracked-Players.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tom +Date: Fri, 26 Feb 2021 16:24:25 -0600 +Subject: [PATCH] Expose Tracked Players + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f7223f214f911dd25abcf3a52745588ec630241d..7abeeefeb579a43bc9ee85fd4150afacfb11c802 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -172,7 +172,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper + private CraftEntity bukkitEntity; + +- PlayerChunkMap.EntityTracker tracker; // Paper ++ public PlayerChunkMap.EntityTracker tracker; // Paper package private -> public + public boolean collisionLoadChunks = false; // Paper + public Throwable addedToWorldStack; // Paper - entity debug + public CraftEntity getBukkitEntity() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index e97ad53eb8bdc4c71d8014d060710cb3a29ab7f8..e5549439b3d4d608cf37dd33b6c8c9e10dfe9328 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -16,6 +16,7 @@ import java.net.InetSocketAddress; + import java.net.SocketAddress; + import java.util.ArrayList; + import java.util.Collection; ++import java.util.Collections; // Paper + import java.util.HashMap; + import java.util.HashSet; + import java.util.LinkedHashMap; +@@ -2306,6 +2307,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end + ++ // Paper start ++ @Override ++ public Set getTrackedPlayers() { ++ if (entity.tracker == null) { ++ return Collections.emptySet(); ++ } ++ ++ Set set = new HashSet<>(entity.tracker.trackedPlayers.size()); ++ for (EntityPlayer entityPlayer : entity.tracker.trackedPlayers) { ++ set.add(entityPlayer.getBukkitEntity().getPlayer()); ++ } ++ return set; ++ } ++ // Paper end ++ + // Spigot start + private final Player.Spigot spigot = new Player.Spigot() + { diff --git a/patches/server-unmapped/0001/0679-Remove-streams-from-SensorNearest.patch b/patches/server-unmapped/0001/0679-Remove-streams-from-SensorNearest.patch new file mode 100644 index 0000000000..d5ee1c60ca --- /dev/null +++ b/patches/server-unmapped/0001/0679-Remove-streams-from-SensorNearest.patch @@ -0,0 +1,120 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bjarne Koll +Date: Wed, 3 Mar 2021 12:48:48 +0100 +Subject: [PATCH] Remove streams from SensorNearest + +The behavioural nearby sensors are validated every tick on the entities +that registered the respective sensors and are therefore a good subject +to performance improvements. + +More specifically this commit replaces the Stream#filter usage with +ArrayList#removeIf as the removeIf method on an array list is heavily +optimized towards a single internal array re-allocation without any +further overhead on the removeIf call. + +The only negative of this change is the rather agressive diff these +patches introduce as the methods are basically being reimplemented +compared to the previous stream-based implementation. + +See: https://nipafx.dev/java-stream-performance/ + +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestItems.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestItems.java +index 418cd6d8b40d35aa3be73eb12f2e3b75597238b9..2e3149a0b15299468079796bd3ea56eabdb4998c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestItems.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestItems.java +@@ -27,18 +27,16 @@ public class SensorNearestItems extends Sensor { + List list = worldserver.a(EntityItem.class, entityinsentient.getBoundingBox().grow(8.0D, 4.0D, 8.0D), (entityitem) -> { + return true; + }); +- +- entityinsentient.getClass(); ++ // Paper start - remove streams in favour of lists + list.sort(Comparator.comparingDouble(entityinsentient::h)); +- Stream stream = list.stream().filter((entityitem) -> { +- return entityinsentient.i(entityitem.getItemStack()); +- }).filter((entityitem) -> { +- return entityitem.a((Entity) entityinsentient, 9.0D); +- }); +- +- entityinsentient.getClass(); +- Optional optional = stream.filter(entityinsentient::hasLineOfSight).findFirst(); +- +- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, optional); ++ EntityItem nearest = null; ++ for (EntityItem entityItem : list) { ++ if (entityinsentient.i(entityItem.getItemStack()) && entityItem.a(entityinsentient, 9.0D) && entityinsentient.hasLineOfSight(entityItem)) { ++ nearest = entityItem; ++ break; ++ } ++ } ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, Optional.ofNullable(nearest)); ++ // Paper end + } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestLivingEntities.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestLivingEntities.java +index d3bb1c02d80fcd3586030b07f84e7ebdd97d873e..0bc17d148e91277efdf72541e5470fa56d455670 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestLivingEntities.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestLivingEntities.java +@@ -26,10 +26,12 @@ public class SensorNearestLivingEntities extends Sensor { + list.sort(Comparator.comparingDouble(entityliving::h)); + BehaviorController behaviorcontroller = entityliving.getBehaviorController(); + +- behaviorcontroller.setMemory(MemoryModuleType.MOBS, (Object) list); +- behaviorcontroller.setMemory(MemoryModuleType.VISIBLE_MOBS, list.stream().filter((entityliving1) -> { +- return a(entityliving, entityliving1); +- }).collect(Collectors.toList())); ++ behaviorcontroller.setMemory(MemoryModuleType.MOBS, list); // Paper - decompile error ++ // Paper start - remove streams in favour of lists ++ List visibleMobs = new java.util.ArrayList<>(list); ++ visibleMobs.removeIf(otherEntityLiving -> !Sensor.a(entityliving, otherEntityLiving)); ++ behaviorcontroller.setMemory(MemoryModuleType.VISIBLE_MOBS, visibleMobs); ++ // Paper end + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestPlayers.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestPlayers.java +index 29abc7feec5358dce7d16958f0c5807f4bda992f..60e4da9217d4d950b5077baf6b50eaee20f8df09 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestPlayers.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SensorNearestPlayers.java +@@ -26,22 +26,26 @@ public class SensorNearestPlayers extends Sensor { + + @Override + protected void a(WorldServer worldserver, EntityLiving entityliving) { +- Stream stream = worldserver.getPlayers().stream().filter(IEntitySelector.g).filter((entityplayer) -> { +- return entityliving.a((Entity) entityplayer, 16.0D); +- }); ++ // Paper start - remove streams in favour of lists ++ List players = new java.util.ArrayList<>(worldserver.getPlayers()); ++ players.removeIf(player -> !IEntitySelector.notSpectator().test(player) || !entityliving.a(player, 16.0D)); // Paper - removeIf only re-allocates once compared to iterator ++ players.sort(Comparator.comparingDouble(entityliving::h)); + +- entityliving.getClass(); +- List list = (List) stream.sorted(Comparator.comparingDouble(entityliving::h)).collect(Collectors.toList()); + BehaviorController behaviorcontroller = entityliving.getBehaviorController(); +- +- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_PLAYERS, (Object) list); +- List list1 = (List) list.stream().filter((entityhuman) -> { +- return a(entityliving, (EntityLiving) entityhuman); +- }).collect(Collectors.toList()); +- +- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, (Object) (list1.isEmpty() ? null : (EntityHuman) list1.get(0))); +- Optional optional = list1.stream().filter(IEntitySelector.f).findFirst(); +- +- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, optional); ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); ++ ++ EntityHuman nearest = null, nearestTargetable = null; ++ for (EntityHuman player : players) { ++ if (Sensor.a(entityliving, player)) { ++ if (nearest == null) nearest = player; ++ if (IEntitySelector.canAITarget().test(player)) { ++ nearestTargetable = player; ++ break; // Both variables are assigned, no reason to loop further ++ } ++ } ++ } ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, nearest); ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, nearestTargetable); ++ // Paper end + } + } diff --git a/patches/server-unmapped/0001/0680-do-not-create-unnecessary-copies-of-passenger-list.patch b/patches/server-unmapped/0001/0680-do-not-create-unnecessary-copies-of-passenger-list.patch new file mode 100644 index 0000000000..4dd406c34c --- /dev/null +++ b/patches/server-unmapped/0001/0680-do-not-create-unnecessary-copies-of-passenger-list.patch @@ -0,0 +1,239 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lukas81298 +Date: Sun, 13 Dec 2020 13:42:55 +0100 +Subject: [PATCH] do not create unnecessary copies of passenger list + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMount.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMount.java +index edc6fff87c4abad2c123b1a46d6e5b792602b3be..5e739b26b6b5490b2c7651d3e9ff8649e776137e 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMount.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMount.java +@@ -15,7 +15,7 @@ public class PacketPlayOutMount implements Packet { + + public PacketPlayOutMount(Entity entity) { + this.a = entity.getId(); +- List list = entity.getPassengers(); ++ List list = entity.passengers; // Paper - do not create a copy of the list + + this.b = new int[list.size()]; + +diff --git a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java +index 58dd349adf2bc9bac6569464ef7a7aec81729e79..1df8fb8cb3fcf8201e1c5fa8ca13f7a9c632c379 100644 +--- a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java ++++ b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java +@@ -103,10 +103,10 @@ public class EntityTrackerEntry { + + public final void tick() { this.a(); } // Paper - OBFHELPER + public void a() { +- List list = this.tracker.getPassengers(); ++ List list = this.tracker.passengers; // Paper - do not copy list + + if (!list.equals(this.p)) { +- this.p = list; ++ this.p = com.google.common.collect.ImmutableList.copyOf(list); // Paper - only copy list if something has changed + this.broadcastIncludingSelf(new PacketPlayOutMount(this.tracker)); // CraftBukkit + } + +@@ -376,7 +376,7 @@ public class EntityTrackerEntry { + } + } + +- if (!this.tracker.getPassengers().isEmpty()) { ++ if (!this.tracker.passengers.isEmpty()) { // Paper - do not create copy of list + consumer.accept(new PacketPlayOutMount(this.tracker)); + } + +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index b47cd2a8fb4920531d80acfcfe40f8211fedc9ae..300884804bf9ac3fba7c30a04d8adf52e3dd2e3e 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -2307,7 +2307,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially + list.add(entity); + } + +- if (!entity.getPassengers().isEmpty()) { ++ if (!entity.passengers.isEmpty()) { // Paper - do not copy list + list1.add(entity); + } + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7abeeefeb579a43bc9ee85fd4150afacfb11c802..429f0591c6a55f6c5d08a0755f7d39da676468bc 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2230,7 +2230,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + protected boolean q(Entity entity) { +- return this.getPassengers().size() < 1; ++ return this.passengers.size() < 1; // Paper - do not copy list + } + + public final float getCollisionBorderSize() { return bg(); } // Paper - OBFHELPER +@@ -2326,7 +2326,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public boolean isVehicle() { +- return !this.getPassengers().isEmpty(); ++ return !this.passengers.isEmpty(); // Paper - do not copy list + } + + public boolean bt() { +@@ -3138,7 +3138,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public boolean w(Entity entity) { +- Iterator iterator = this.getPassengers().iterator(); ++ Iterator iterator = this.passengers.iterator(); // Paper - do not copy list + + Entity entity1; + +@@ -3154,7 +3154,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + } + + public boolean a(Class oclass) { +- Iterator iterator = this.getPassengers().iterator(); ++ Iterator iterator = this.passengers.iterator(); // Paper - do not copy list + + Entity entity; + +@@ -3171,7 +3171,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + + public Collection getAllPassengers() { + Set set = Sets.newHashSet(); +- Iterator iterator = this.getPassengers().iterator(); ++ Iterator iterator = this.passengers.iterator(); // Paper - do not copy list + + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); +@@ -3197,7 +3197,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne + private void a(boolean flag, Set set) { + Entity entity; + +- for (Iterator iterator = this.getPassengers().iterator(); iterator.hasNext(); entity.a(flag, set)) { ++ for (Iterator iterator = this.passengers.iterator(); iterator.hasNext(); entity.a(flag, set)) { // Paper - do not copy list + entity = (Entity) iterator.next(); + if (!flag || EntityPlayer.class.isAssignableFrom(entity.getClass())) { + set.add(entity); +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalTame.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalTame.java +index ec51a5532576ce25465bacf61c716ddaffca514e..d285f5aa66fa81a2f56920c05afb4506cb82fa54 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalTame.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalTame.java +@@ -52,7 +52,7 @@ public class PathfinderGoalTame extends PathfinderGoal { + @Override + public void e() { + if (!this.entity.isTamed() && this.entity.getRandom().nextInt(50) == 0) { +- Entity entity = (Entity) this.entity.getPassengers().get(0); ++ Entity entity = this.entity.passengers.isEmpty() ? null : this.entity.passengers.get(0); // Paper - do not copy list, fixed array out of bounds exception as well + + if (entity == null) { + return; +diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityPig.java b/src/main/java/net/minecraft/world/entity/animal/EntityPig.java +index d6e1697f64e60f2a567288c604a1690159955f37..676ca381a5e111fc15f319e73504e4e60dbf0d2b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/EntityPig.java ++++ b/src/main/java/net/minecraft/world/entity/animal/EntityPig.java +@@ -86,7 +86,7 @@ public class EntityPig extends EntityAnimal implements ISteerable, ISaddleable { + @Nullable + @Override + public Entity getRidingPassenger() { +- return this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0); ++ return this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java +index d678e3164ecdb7f0c600597bcb39d1054dfbc4b2..1e41c45af6dbcf097d7d6104e63db637f199301a 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java +@@ -972,7 +972,7 @@ public abstract class EntityHorseAbstract extends EntityAnimal implements IInven + @Nullable + @Override + public Entity getRidingPassenger() { +- return this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0); ++ return this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list + } + + @Nullable +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityRavager.java b/src/main/java/net/minecraft/world/entity/monster/EntityRavager.java +index 7781386da593a1c4f3ad7e7e8761767dda7cb66c..16d5cae64887b82e67eeb61ccb714e6125ff0c09 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityRavager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityRavager.java +@@ -134,7 +134,7 @@ public class EntityRavager extends EntityRaider { + @Nullable + @Override + public Entity getRidingPassenger() { +- return this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0); ++ return this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java +index 069076d3c7165440217a7632b089ab2aa0fbdb1d..5e2c13bd6e52ffe182ef034e05ba6fe1cb301005 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityBoat.java +@@ -317,7 +317,7 @@ public class EntityBoat extends Entity { + super.tick(); + this.r(); + if (this.cs()) { +- if (this.getPassengers().isEmpty() || !(this.getPassengers().get(0) instanceof EntityHuman)) { ++ if (this.passengers.isEmpty() || !(this.passengers.get(0) instanceof EntityHuman)) { // Paper - do not copy list + this.a(false, false); + } + +@@ -380,7 +380,7 @@ public class EntityBoat extends Entity { + Entity entity = (Entity) list.get(j); + + if (!entity.w(this)) { +- if (flag && this.getPassengers().size() < 2 && !entity.isPassenger() && entity.getWidth() < this.getWidth() && entity instanceof EntityLiving && !(entity instanceof EntityWaterAnimal) && !(entity instanceof EntityHuman)) { ++ if (flag && this.passengers.size() < 2 && !entity.isPassenger() && entity.getWidth() < this.getWidth() && entity instanceof EntityLiving && !(entity instanceof EntityWaterAnimal) && !(entity instanceof EntityHuman)) { // Paper - do not copy passenger list + entity.startRiding(this); + } else { + this.collide(entity); +@@ -727,8 +727,8 @@ public class EntityBoat extends Entity { + float f = 0.0F; + float f1 = (float) ((this.dead ? 0.009999999776482582D : this.bc()) + entity.bb()); + +- if (this.getPassengers().size() > 1) { +- int i = this.getPassengers().indexOf(entity); ++ if (this.passengers.size() > 1) { // Paper - do not copy list ++ int i = this.passengers.indexOf(entity); // Paper - do not copy list + + if (i == 0) { + f = 0.2F; +@@ -747,7 +747,7 @@ public class EntityBoat extends Entity { + entity.yaw += this.ak; + entity.setHeadRotation(entity.getHeadRotation() + this.ak); + this.a(entity); +- if (entity instanceof EntityAnimal && this.getPassengers().size() > 1) { ++ if (entity instanceof EntityAnimal && this.passengers.size() > 1) { // Paper - do not copy list + int j = entity.getId() % 2 == 0 ? 90 : 270; + + entity.n(((EntityAnimal) entity).aA + (float) j); +@@ -907,13 +907,13 @@ public class EntityBoat extends Entity { + + @Override + protected boolean q(Entity entity) { +- return this.getPassengers().size() < 2 && !this.a((Tag) TagsFluid.WATER); ++ return this.passengers.size() < 2 && !this.a((Tag) TagsFluid.WATER); // Paper - do not copy list + } + + @Nullable + @Override + public Entity getRidingPassenger() { +- List list = this.getPassengers(); ++ List list = this.passengers; // Paper - do not copy list + + return list.isEmpty() ? null : (Entity) list.get(0); + } +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java +index 57821301ef031995e6044a17b46c70a693322455..75a88ab5d5b0fdb98ea8d61bb6b82049b21101f3 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java +@@ -561,7 +561,7 @@ public abstract class EntityMinecartAbstract extends Entity { + + vec3d1 = new Vec3D(d8 * d4 / d6, vec3d1.y, d8 * d5 / d6); + this.setMot(vec3d1); +- Entity entity = this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0); ++ Entity entity = this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list + + if (entity instanceof EntityHuman) { + Vec3D vec3d2 = entity.getMot(); diff --git a/patches/server-unmapped/0001/0681-MC-29274-Fix-Wither-hostility-towards-players.patch b/patches/server-unmapped/0001/0681-MC-29274-Fix-Wither-hostility-towards-players.patch new file mode 100644 index 0000000000..53f85dfd86 --- /dev/null +++ b/patches/server-unmapped/0001/0681-MC-29274-Fix-Wither-hostility-towards-players.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheShermanTanker +Date: Thu, 1 Oct 2020 01:11:03 +0800 +Subject: [PATCH] MC-29274: Fix Wither hostility towards players + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index d2f6e1308a4dfec663770e2c7f4de0cf22402a97..3e6132211912d29e34c94042b0819f11a3bd123e 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -804,4 +804,10 @@ public class PaperWorldConfig { + private void setUpdatePathfindingOnBlockUpdate() { + updatePathfindingOnBlockUpdate = getBoolean("update-pathfinding-on-block-update", this.updatePathfindingOnBlockUpdate); + } ++ ++ public boolean fixWitherTargetingBug = false; ++ private void witherSettings() { ++ fixWitherTargetingBug = getBoolean("fix-wither-targeting-bug", false); ++ log("Withers properly target players: " + fixWitherTargetingBug); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java +index 891905712903bf3ba241187791cfa995375430d5..229eabe0510e6c3660236ed0fb3e80d41074642c 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java +@@ -104,6 +104,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(7, new PathfinderGoalRandomLookaround(this)); + this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, new Class[0])); ++ if(this.world.paperConfig.fixWitherTargetingBug) this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 0, false, false, null)); // Paper - Fix MC-29274 + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityInsentient.class, 0, false, false, EntityWither.by)); + } + diff --git a/patches/server-unmapped/0001/0682-Throw-proper-exception-on-empty-JsonList-file.patch b/patches/server-unmapped/0001/0682-Throw-proper-exception-on-empty-JsonList-file.patch new file mode 100644 index 0000000000..a65efb398f --- /dev/null +++ b/patches/server-unmapped/0001/0682-Throw-proper-exception-on-empty-JsonList-file.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sun, 1 Nov 2020 16:43:11 +0100 +Subject: [PATCH] Throw proper exception on empty JsonList file + + +diff --git a/src/main/java/net/minecraft/server/players/JsonList.java b/src/main/java/net/minecraft/server/players/JsonList.java +index cd35b833d3047a38be980ee550641e87bd3b9b01..c960852dc60d0598012c5eef0d139fe38bde63fb 100644 +--- a/src/main/java/net/minecraft/server/players/JsonList.java ++++ b/src/main/java/net/minecraft/server/players/JsonList.java +@@ -189,6 +189,7 @@ public abstract class JsonList> { + + try { + JsonArray jsonarray = (JsonArray) JsonList.b.fromJson(bufferedreader, JsonArray.class); ++ com.google.common.base.Preconditions.checkState(jsonarray != null, "The file \"" + this.c.getName() + "\" is either empty or corrupt"); // Paper + + this.d.clear(); + Iterator iterator = jsonarray.iterator(); diff --git a/patches/server-unmapped/0001/0683-Improve-ServerGUI.patch b/patches/server-unmapped/0001/0683-Improve-ServerGUI.patch new file mode 100644 index 0000000000..ae8862a977 --- /dev/null +++ b/patches/server-unmapped/0001/0683-Improve-ServerGUI.patch @@ -0,0 +1,400 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> +Date: Sat, 3 Oct 2020 08:27:40 +0200 +Subject: [PATCH] Improve ServerGUI + +- Added logo to server frame +- Show tps in the server stats + +diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +index 67d064e3959ed8d886df30ce9d97f86c2443fa39..dc6bc1910ad0f9b27144d5750078c3ca607d03d3 100644 +--- a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java ++++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +@@ -57,9 +57,18 @@ public class RAMDetails extends JList { + public void update() { + GraphData data = RAMGraph.DATA.peekLast(); + Vector vector = new Vector<>(); ++ ++ double[] tps = new double[] {server.tps1.getAverage(), server.tps5.getAverage(), server.tps15.getAverage()}; ++ String[] tpsAvg = new String[tps.length]; ++ ++ for ( int g = 0; g < tps.length; g++) { ++ tpsAvg[g] = format( tps[g] ); ++ } + vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)"); + vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb"); + vector.add("Avg tick: " + DECIMAL_FORMAT.format(getAverage(server.getTickTimes())) + " ms"); ++ vector.add("TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg)); ++ + setListData(vector); + } + +@@ -70,4 +79,9 @@ public class RAMDetails extends JList { + } + return ((double) total / (double) tickTimes.length) * 1.0E-6D; + } ++ ++ private static String format(double tps) ++ { ++ return ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); ++ } + } +diff --git a/src/main/java/net/minecraft/server/gui/GuiStatsComponent.java b/src/main/java/net/minecraft/server/gui/GuiStatsComponent.java +index f85a4079bb2931fd29a526379ba350eb7e8ae636..79cc587d8ad9f576f9e4478a41baabcaf690c568 100644 +--- a/src/main/java/net/minecraft/server/gui/GuiStatsComponent.java ++++ b/src/main/java/net/minecraft/server/gui/GuiStatsComponent.java +@@ -18,7 +18,7 @@ public class GuiStatsComponent extends JComponent { + }); + private final int[] b = new int[256]; + private int c; +- private final String[] d = new String[11]; ++ private final String[] d = new String[12]; public String[] getStatEntries() { return this.d; } // Paper - change size, OBFHELPER + private final MinecraftServer e; + private final Timer f; + +@@ -37,8 +37,18 @@ public class GuiStatsComponent extends JComponent { + private void b() { + long i = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + ++ // Paper start - Add tps entry ++ double[] tps = org.bukkit.Bukkit.getTPS(); ++ String[] tpsAvg = new String[tps.length]; ++ ++ for ( int g = 0; g < tps.length; g++) { ++ tpsAvg[g] = format( tps[g] ); ++ } + this.d[0] = "Memory use: " + i / 1024L / 1024L + " mb (" + Runtime.getRuntime().freeMemory() * 100L / Runtime.getRuntime().maxMemory() + "% free)"; + this.d[1] = "Avg tick: " + GuiStatsComponent.a.format(this.a(this.e.h) * 1.0E-6D) + " ms"; ++ getStatEntries()[2] = "TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg); ++ // Paper end ++ + this.b[this.c++ & 255] = (int) (i * 100L / Runtime.getRuntime().maxMemory()); + this.repaint(); + } +@@ -85,4 +95,10 @@ public class GuiStatsComponent extends JComponent { + public void a() { + this.f.stop(); + } ++ ++ // Paper - start Add tps entry ++ private static String format(double tps) { ++ return ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); // only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/server/gui/ServerGUI.java b/src/main/java/net/minecraft/server/gui/ServerGUI.java +index c2c075b9e3b70f863b6c450e4f31d6fde2935be6..d1d98c2f192514a3f511bebb00c088b03e6be84c 100644 +--- a/src/main/java/net/minecraft/server/gui/ServerGUI.java ++++ b/src/main/java/net/minecraft/server/gui/ServerGUI.java +@@ -31,6 +31,11 @@ import net.minecraft.DefaultUncaughtExceptionHandler; + import net.minecraft.server.dedicated.DedicatedServer; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++// Paper start ++import java.io.IOException; ++import java.util.Objects; ++import javax.imageio.ImageIO; ++// Paper end + + public class ServerGUI extends JComponent { + +@@ -56,6 +61,15 @@ public class ServerGUI extends JComponent { + jframe.pack(); + jframe.setLocationRelativeTo((Component) null); + jframe.setVisible(true); ++ jframe.setName("Minecraft server"); // Paper ++ ++ // Paper start - Add logo as frame image ++ try { ++ jframe.setIconImage(ImageIO.read(Objects.requireNonNull(ServerGUI.class.getClassLoader().getResourceAsStream("logo.png")))); ++ } catch (IOException ignore) { ++ } ++ // Paper end ++ + jframe.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent windowevent) { + if (!servergui.f.getAndSet(true)) { +diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png +new file mode 100644 +index 0000000000000000000000000000000000000000..a7d785f60c884ee4ee487cc364402d66c3dc2ecc +GIT binary patch +literal 14310 +zcmXY21yoy2uugDycPmm{N^yd_Q`}vP7YOd|PH`>8wLo!q*HRn`6f5rV?*HD)IX5{c +zxpy-=`|Zxo_svGBD$Agwkf4A-AaprdNp;|J21vifLD%hMrT$lZeEex|7Xe0mqFAZArC{!qp)zJmbab@utqA`6 +z61Z~|e!k$IbXNT?PvGuuzT7G514$8e!}lsR>%nURMm+~pde``@(!O=ISt0%B93;Ez +za-qRi4n0Q>zQ2#2^_y08QOl3jT*!Ir5@<8VrFx(6f9sP|H8ttjftN;wrX>jP4BcG1;MfU5x^L`zc09u!bDBt#+ll=7@ +zB;}A$BKgu}V?#qfHvm`~pt%wG2y{MOc%B!8I`p|pc +zO#?sq!Zd&j8UPmvY4RQnfo>!6{a}GFV!}g@qu<3Wu$07X(O`vikNW$~q!ngF23Ls2 +z53p8js<-B_Qd?xX6rtq43Mdz(jOg2QXx#Wng_9^1^^~KqFNq{Kvb@Ap9}bf&xFA-C +z5+#cQ`#v$A=kd0O=agATcleBaxXf_(dnqbQz|cL9R&&Ni1omTs+6~YApmk)MCghxj +z1}mq&IU>1nEiF=q=PI`%jQbyRd=hVI83Sm{E-4uTc#w;NNwEW)C(C`xvWzY_%`_MmO +zD&g-sEaE)}6(&g)y-N&rNy;5@+{M`}!{60Y8wMgF5;HmO#B~hG`W$;7xLG*yF((rq +zxP6I#r#o`B3FppK{v(q1!C+YLFSfySDcHyoW!}EfzuCB1B|C5+oP}dtocnwkcNy1EZ6#5JX4=ePl&cu~0tMnt&79+I4%PaK>VqFx;r!QdNmnxlEqdU-QR%Nmu{aWP +zJxwXvt5fFTCOVgB)Zq +z%H0U=9q7Y0lu&1kc4zYT3*lHA@XJfoK>3WFM&WWf2u6^+wCm8##D$x@Gkw+t^HoO( +z4pxDRqg;$5S=t^k22H5^V3V0Qfy%Ogl8I%LD$52=7)J>Ki9Ej1HyEi_ujELlz8$-+?cdD1Zxi02kW0 +zaY=caFq4~s^R?zxcc3Z0X|az}Aww<{P$>6rk+5Di5J7$kWor0{Q&>+DWSBH^Gf`SP +zT{4}IOFh-hB7xwBdewq%de)q6QvxorV(()2>@j8i!kj)=^hN +zl_N{$9xTHHA;V&Zx#tX&1pOO;v^NiOP#_UK@J;;lp+OOhOOO2mlMdxM;Qv-mWG+^vzox|8t`w| +z=gPlM3)y6G*hfV1WwuMe>bO-vP9g`h5BqgO9x{ROBD;aPl>XDmvt(3PUxt|4RFRpK +z5OEtRz{(Oa_W_!Z4XHf#h;Z-~71XM7wlF*L!-#h_Uy2tGuy-rAZ)4{qE~feNkp}qf +zgvBtLkFPI~I7%C=OHZfPZz$j>L9)rb;l +z@J^dxncy52;wmHg=wC3|Xn6jPYCR7xc}~D0wNjoYxmoRh_zh=6@8coM1UQIa_z*1)cZPw4v40qoZQp-uy#DLv=oP +zX9b3vzFA2r8}|_AO8W1(OMG__0{1AUD&Z%&7-(>s+Z-X6Sv}G5QguIbZ3mYa--?09 +z;wNw?n=yAag4%m#w$$-YZ{(ZJUcwHfzu&!gykNjG)e}!=q8xy2_KS=ULsQwv45NK! +zVqqD8#S{vRjg4(Q6HM_F&tihNIQns<%DVjE$cv33ET>Dvc^#{z&#u&&9RgXO?ZLuebczKv#;! +zCS|2lIa37Bp#3RWj0$V3=I2>o40{(J^LD|EUH?!2;Z&HS*>7*V%{v1)wHaUP85mcX +z%q!K}Ntr*IzJD%++btJ;VQO*OjJL1t{GvR3cy@OC-~pe^bV?N`z0QKCr?Tom)4u%A +z3mi2k&eIgh0^rGI#Di+&3lrsy-r+}zwBkDQtswtPbkj!Y^l`{f!# +zLseC0M;DiifDa!({-G4{W$Wxsgv*(NX%HMyXhArVwY105dUHg?+=@6Sy8n@slS76x +zU7%PI8ToKm#qahfR;7kn#|t@9y(0EkooWBDqA1(mpO)>BBz))giBi8xVHlj#dR9U8 +zRo%`iBdlj8%_tRn^qa%T>{nsLLwTNld&WHLyfbPzv2W62m6q=Nsdxnk +z#{P==5!Lidx3bcr_qlUl%BX!xjywA?jv>FU^mJDa0zQT9Kw8RRHq>7B +zb~DXw0(oqBrOQunsm2ghWV2i1VmN{F?)U;0%*j{FEUxazAJ3)KSWomuhklkDi?5h*MTLDS5ma_Nk1sNZYzZ#$maGRyiXBzjG@(G__fuyBl(^A>s&{jF+J%5| +zv#7nD1XK806#_U_4#N2ANAxznk%;U$Y$z#{K*O07mADqx6LjACqwP<`HFV#C6Q*wx +z8JVP_qGF}V7B?^8)f*2F5AON7v$L~Kr?2}oPai_kG!_6MI(U`LS~+Mo*CSyrw>pPE +zllqxy +z^&rnDn4XA@AUY7~`1lwTCrm8KlVRqX&!kZFH&;i9@=R}UDxNSh*)Iq2U+#9}@ag1t +z%KUOEw0DXT)>hQoLTprY^z=BC=8NAyi3pZWT7A`?;rI<3%65Nqb93%pJ=!+dNtB>W +z7f3O-e-S7ZBgBntcyt~wOG_p$AU2zlGH8=%TEm+z8kLYReEMTkIo#2YiA=iKWrH); +zS%uT3xAyyY=!U)0Evpgx{{38MPR2nN<3913M<0O#YCO=TSt^4IzV3^D%2zC>t_OO} +z_h~AVOk+IIi$Ov;-g93a4j@WaekCC#HFm2_Vu9s)8-GbYtr{LgrxnSIN^PW9)!jYX +z?%-yssA~&R3F)C)wj5i|@!atCx?Qy%P1QEGSZm;iUNai`-F(8a%y+_a>CMzx$XEKx +z>sW|JbN36s+Y{4SZsrspH%UH=+Q6J`c&_-JLGL&5|$XUA1vFOC+rgoc&xT{dFT&pMaEBKwyD;plX0>2nla;jTlQ{!fn2M=Ak*=K*g% +zBm0-$ly1~}CT-5gv){jex9)7&b8u!a+vYHXU>=NF2>g3+_rN{(LUMGwRWKk49sS$v +zazyX8zZ1hwZ|U*5{fK@i@hRl*U%Q2cg+!iIfb)6W%S5F{91qinEZE%~4Gl>rBw9S< +zMP5$exl1jESyt}d~jo?hf`z^32b!}UGtJH+w9(0UrI#~Ei*ii&6z(AVE?(}k_A +zE9Z@mj7HF-ch46I0ipe3gapRj{=zk_J1E^b_JwdrhKi4ytBuwP)m>e$@9v`A{1N{h +zwUN6H=_W+h(a?rGaQ%%LP5C4)XiZ*`1uUwgqWvk`LyDD!Ps#Q5oI($KDJ%8n5kBi- +zghsLx`~mf<>WT)6-cJBbp|htk1NfkZ@e#B4@l?UH7!MDMpO?1NETGk_Eg{z!N3!D< +zWg8gtgS%b(0Bg7dw9u35xq)1vNdnM8iu7Eje*u?#sZ~%^q*HDaZC?5z4ZzhSA%ndS +z4&$M&7(|(9nWY%QShCnuN0 +z`n9&UeypypUgx;R+x;XM#8uDM{p`9~j<49)^dotHJVO*A@HL&g7F={FP#trj@{dzm +zeQUiqRWJ&pkKkA1O-|vOf8O1UQ$$0lIExffio|}F@ROV#MXcPH$ +z?$$kxAF@B#KT}u;R@SVyIO>1sw1!i?C(_013w9@?8$bKaLQi34zC$g*^}F&(%NEO6 +zQzD-^6}HQMnGJ{h$J*)HjSxjblWegsW&rLC8Ov_r_20jLjUS$Ptnm|p9fK%r0j+4; +z57^mjL&lISh8>DC;eB$B69$h4XxE3qU4T&zUpDeV@4g>or%D-x@qhie>6mqD959ck74(h?S0BA0}YQ18d?hr6}%}y{%ZNJ^-(?=Op~; +z#2-UNh)jH9>RXmvPJ(Y!8(uhyW|sFpyvv)AaNeljHj^Fx+RC +z!`@c->W1C^FUKHmG2w_atkdsMnzY+l!CV8havQ8-Gu)<8t{#V*2Pwp4h?ayXsi5Z> +zo!guta>TA~iv#iJpQkN>#)QF%As@2WgU&V_Y^qm#E*O}M_ijJfFWq}ts)-l4>D)kCqJJ@MG2$69ph0jzwI8ry1u8D@CyinC$oT?7S*Z}Eg +zYs}PWLqr4u@)w}#!{cMx;KxO6W2H6~3k$laJjAt+C{0mmCRnfs=OJYbh}HMh&e`#> +zj;jrpjqKCh41OK{FOS`@_sPP$iCm46G^EMNk8(l-1f>!gEV+4vMVRZ#8infUenP+k +zL^tBOHF^=)k&U-Tw{gfijqQ&^ +z-RHHII5yp}2|o8pTsf6x7$teW9Em!~iy2DN?D@|U)g%I6VG%JBO$|~;c~1Q^3|x`1 +z6HRbq1#~Ke)wWpALcc&@P;m+*sGavR0{aOx3=IwUE3YPWAwV45pzD$~02inxi7(6X +z$zk683M=_r#M*+6fQ)&FK0y|lm7JLwS)K=t&ZJk!U_-y%_o@fhr{s37MUEQOF*M)3 +zB$;4>Zx;Xk*(hwFjb>1iJ1f*D#nyWL{=>{2|9*^vCNN!%bF8Oe<`xz#s;jFz?;I}4M3lL;!fy_;J-E96Of+;sG%K=fZdR)99pJ}fM( +zq%(s8UrsEL{NrdF`!#RY+VjFyPpE_vtqPMM!MQ+QnE)+_g9Z^{4^;k&Sa^=w*yuxB_*Z!U%!3{_9Qr)Jfz4IeS#io4oj_Kqhq`HCUub|Ke!v$1-$v=kc+O#rlCej?%dhY +zxxKUTsFPG1nfoFp3%7@gh9S?vM0N27#*fpJyaX;Vy{!pt*}!9_mX9uC#J5RyjknW2Dm3dCvZYU +zSW?0kvI9!o2un}*%`AYhr^CQT1aZF=-Nt^atn@Kt%b2!hT(pK!|MclbBv3-<+6{>_ +z8toMfWc9rpOk(8|KW>Z-k>Fr(xc_+q9ocf`8!_n}XYUrW?Ax|*_|=5m*4F0V+46wJ +z1IGS^Z5t=0Zj86J2MfJc +zUq#WKCfhoB<;P2&&`*_G4^_0uqDR20m!>T8ay_rxSzA&9_v5##g6tzXTkx+KRfz32 +z9vvpp?+YxHTxDthCBu7)&Q052y4s9*$M4_2w-OdPyK?F-EBoUuSsIk@@(!gA*A_!0 +z2eu1y;-Q$Ut(M>8FCOtw?vZR-%*ly^x)<95vK@P0tJoZws@+M*NGhg_NU`!}DZnWBHQz%*@6))$BWN;EM0xAF+B4Mph#S??J?K+&viwPmes*n^HGDL9iBf +zCk|mDu46wwughN!isu&G((DO>Ws`(VLY?^#w=RONxUgFGby--Y=5NJ|(>qXOS`;lZhmXyMEyBdVM@jJh71E-})~`?t4w8^Kwy) +z<+KACjs!F^TS-;FT24_iWF+=l(nR}j7U#;Vd +z)IT3=b&}A}1PUKFa6DKfgHkJci!~7u?a%k9h7Rri^{y`|;;xNDoQbV}+oJ=LdApL}|77o@C= +z;~aed)XpbrMtt1x3gHPWxbliQH4nKBCew{9 +z*-_PTyn~`1VrwKcc4ZrhI^!MsZ{D0O0%O2!SHHi^Dfyr9*x*DGFKwc()b;q6nM*M7 +zvA$x_?$BMJJHN5HIn9Ps{_7-sn79~BZegaa5V;s(BA<5BnU?^AeJHXtd)cIj_UCjA +zW|N@MjV~vrJz{sE0Dzv}tXxUDQAXm)1(kX7C_ZVFX%!TlZ850i(P1A0BxaJu)#LcH +zoxMFRzxoxw$bM=B6gpuMD#vcsa^00?%=D+T9-dQqV*=zD|)W!3BLun2&^n)~$ +z2_^{i9~sGXOAsF_S=k&4mWJ@`mD+G%MiPTlhuomboeFNwHb(< +zVpVR!mwf;JmpO3JL|B%L-!;@7TG}+`HZA;-{VIlQGY|T=f|!9!S=!c?sq5|KeEQ*~ +zm!1xeZcJPbSsfjU9e>K|=Ni<+YgrIG!|5@|Z>4bjx+`1j^O-{QK8XARf +zUG$nLRiTEtt;)9F30rvw>nj)@vCF{$d7>o2n>}~Y2^^C79l@s`uXRZOcuy>^%2@t- +zRGv={pKlDXFUgvG_^DWGR==il1rIzn{$p4r(FVOQxZi!_*Ksfl2hR{Aj>01RbFAM= +zpr0wzMwlOwlkt4|JLK)$>VL+{4nv>^`yMa)T;(9f*B(9;{T+)_=M4dN>M&&hS-#(G +z)-sW(WxVkHR)`x#g)25Lu7qnN;~Q-bvKDZ=;^fyLy@okDpvt&ZU{!U)WVtmnp +zAN-CzM{jPFWep9NAKDDq@=kynkGi_GQ@Z2y_Wn)xc_q3-&+9`qdGy_{PF-2c^$)%x +zd0sonEJhtG*2|P*Q-f_3`Akk96HzBz2 +z!5tnJaCcA2hGQrSw*{F)epvfYX?7toP=O0dN +zizY2w`>O@4Vqff!dBhQ^><#TjMP}loM9ProiD-Og@$V=*zQ|Avg0D!+96lr^u(1fl +z3J52PHoJYDdvdiIW?q?JIC*r?88VruLx#bp0lys39v$(c6uC*j}2IFFh +zViOX|K+DH18cd9%Rgjs$*sXuoW<>p^Fv-7CV|zpgTUnj812pyyX-nhA4TZ^UyYY9; +z?}BOarTT1q;0xSTjV_DPWE11?Y2+wSA*ybzebDoy8JwhznKa6SvYxE$WswX7Z6pG$ +zsA2GgHFFL3^zA@XTYK{a+6$Q8di%@1-|q9U15y+~R-L7Kwx8*xr(FP{g*JDPa`e((jSl#~?Rx=3ne(nLfeP9k0grubJK +zU4euzZqt~$Cl%k^{-!e6YQZi|D3#+MUS}VsYZ)0S>y@)kyqRI?A_esvAu-{`1Uq@! +zC+b`wnMK&<_mitl+k@e*$*{&S>vayX*>D>Q5sw2FZ?l(8ff%(8lo<^mBMrwQXOXe+ +z*7sZdWzBTIwZO$y^F)qZL1XbOMY<@M_a56y{({Vg@YN<_y}toq41V%~w=+4ZQvg)X +zVw~l$z-sId^nKU%dlk7W(mG}eS&KV2BdYqNJnX-p=YrG&&`_m0fzA_|iKD${5?oL* +zdS$heR@%Q+(3!!T&k;tIN|v2j=UI))rgkvyC7MTTrKP3g>Fma@_R0`GE5(tL%sS$7 +zG41ag%(Y(xZ5cjlk=R~(3XC+$25r*Fo=G5OhGgR}i!nDoG?^sult?Eo*x$x6CH-3L@LtZ0dfq!Bbbw-S}RwlN%lpH8c=4l2qH +z1wRszHSPh~=esnWvXD8B{D4<}?}6cA+@Ob1760Is6`g!zl@WL(L&={LA}SxAt0>Tw +z%b7i^&yNKM;(vGcNwuxAK{g|S3Y1&pH_6U1G +z3M4zx5FU=O;=l_?VzQ-~bx~xN1axPgYI0am3d25BjYmfSTX7Q}==Vcryl6@Se0(Jv +zxKW_o%H`jdnC7QXlkFbCsACHN1Dx=0gf<~@PW-&<=`1Hd)@#ypH7%OpalDj-P=ts+3^~yWs~TV}BD20HjkW6zc1L +z0#HzMkn3JV%7N-18_@tgE82*YnmEzxirriDSx#_|<|q1vL{k}7>^mRzO(ueTSN2~H +zG}kxp)Qn!&)><3|e>62+GXSpQKcemfqU!&BHZ5Ca;DT<63bBM&uV1BDS?MM$M;x8w>gShAPMxJM^BbMZn}Unm{OC9^4x3%% +zlmX8!km-u$N4fQXQ>jRe`7)3+RFGjhz +z18zf(Fo2<>YV^7LJO^UTZ2Ivd#mpN}o?7pBV&q=f%ID>haV7M8R3jsF*@a%iwIy>| +zsZ!-y{!%&j7`B?W8TcF4NH-RHH1xZ{;7BsA<#APu!;cND)te)FhoXz$BIU}2&^7WP +zT}TX>ZO58$VNPuh6JV7~s(W$vAj`^%AtUamex3YdVl3~4+pqk?G)qUibNMrj0*M25 +zY>5Ac|Dnv6xBQmV#$3JA?&HTN(lYl~J}@$l{*TY^kORrCB)3dDO}^^v!dcLf^CHty +zanjllIQeSLmpuG+h&ae`r*v!C*0A&W^a&q>93?BAXzG7n +z2*3TGPIcN`-_hY9&oaiv#fiv~>}7`T`4=pInEqWX*3e8+yPm^9h-tr&ts55$l+388 +zW)~F}2JH!}VLbQ>?6~H@&k`MnSsTeVj0TRVP4jGbP*!!CwM6`Z11c)yI2w$+R0zxo +zT|obYS1&&`{>>Z9(jnVU&=yI*%PGe*f78ie*_9oap?sd7fx7{r^WT>=XHF +zl`f{=UJEn2?tRw`Fem?eRE6#*nOes(ebRcmaK3~a3{a3EyE1zXSF0p7I_iDJ&%;3V +zU;AS}e?*mH#Yh2P9E3QBigIqu2iXf=@t)2+I~f*_E^JtEP1@IR{CBfTj%T}E3e#n% +zUa{@vU?D$l4DEANwkkK@ruP4ta)E*e^KLGg%$PizyPmHvKNMWtuJQ6sPXY=(1m#>W +z7V?9E!Vj}>a|KfQx5ESpH+q6$@gAp-P#~lbz`aj1_?xinN>3o8b2-Z3w>UZ3QZ}W0 +zWg-!>p>AADDcU^4;0*L4UFgB0QLlXd^y1E&4>txV!T|!`RwjZGl`;-4ZgFf>luHIy +zZ8d8Rh{I3r!g-ht6mAZxMB6VxRqnA0UY`h|mJZy2 +z17BazT$jMKFL3J6Ue_HL1^)4s%$Jj~Qx~1HG#tS@kwL(KP_ZI3dWz0SH(sqj#-*TNGsIWqPj>cj?!GyWvfdEiNOu4$>MIqL=F&Cc0{g*~L5 +zA1wt)=_zMFUkCT5$l!G{1-Y9QtGQ#qm5E(3fYPms_EP*sSVI)bfXN|uNO`BqVuCvd +zv)z8IGRgtM1<_trndVhQ^xA)wn~*W~#d*X@E=W)jcQWI8+?kdzHe;DZ`%+JE%gE}m +z6H=FO8rJxM{N90S=Gi!Mel)TyanxPa;E}C?hJl@e9UWad->;S|v;axgFjrY$z3(rV{MiJ}3M)t;Q?P5wZy0e3G{dcDO7n}3slDXLMrB$;#*W@Qv)D$=?Xs$F(8eTcyGIQ~IWgD%Gn&E>F9y#o>cR-7spE;Rur<_E~Pu)e0I +z#&y1|@8D~8c55<|KMf;&x;hg!A%VOZ38_+uk`jH4#=b9M&xcpxV-7cMN{jXVRnKSe +zlKJJ%=VBV{$DNeI1QkiA;DfdVT?$;O#22z6v6bTK9)fjrfIh!Hq__l~KzuNqT{&kA +zKs@YV6^1ZLGjTgR%(=NHS-DvWnnP)NM#qbHINqmQdCE5??co$3nuikqgm=s7*#Kd*+j_weKrZjMeLeHEoiJm>zuDRU` +zh~ggr^knneWU!Nn}AQt=0Id6Hk; +z4bJqse|V$H`stT?NS0yreYvaZ9YF!fw+N}{3#yXRU!C7?exl35BDC%+!jDMGT^DN# +zN9FGd#5t#;$h}5UgQ?q-Gr15>C6=nLUszle9<+_!!oi_m@_L^-R>_Qty7_g|C%m|5 +z-7^5X5V_ARi?h9_LW%2vByD3X_IvUktqBv{%SYXO1&;e&O#Ll_cfC`Wv1u+l_#RI< +zQ5Kly0;P`%TXaQN(heOg~>V&L{d+ZDA%eq-UKo#1)$rkjSm=nzAE2r +z5--RyKhxfXoGVU3^ab{5XGlyL1+26foG)4HZvN +zG@&I3h0fnK5lIjcrg*XxPy1(gK3_TN`&VYnxP;C|j$~0rT$0f|*#=OzM^NbE-1T5D +z%Csnt)n!sx3N#b(8G&+G3W~Q_B#StA6jZZ=p#wuu`DrAMXm{T@#S;ku4Dme@{Njmk +zCtrh3z6O>o)~o{&Htx+6kn*)$NNBH-biu^aYtWUq +z(G>4rCEKr#tO>!x8A@%W@6g)Xs%2Hq!y#Mbb@9R2@GDWi&!{jhZvzQ1D9nMuPoOS+ +z+cj{9nx5X{jJOIavbFf)Kz5Jnbe5Bu#(XE-z$j&iaP%c9W59OoT0~|N#D*(N2kz={ +zs(|)nH!_+_g1)#ZH2xk>ZTG#6WN#qa3BxZM{NWxq`*#$H255k6Ky?hw*hSA6`c_fl +zT@Ua%E5Ez3;~`kQFmrC#$Nlvc_Uy3#yzhd-6UYuuIwgIBZZC-`dwOBJbfurL(FfhH +z{YkjE+9OrOveY`{t{sGw&51YO1@{iO4)Ki=!Z5#q=m_Hi)_j0`>?;t2j);vv%BUif +z;wpTZdLQLsGvZ()DCdxYudn^Pt;BZ}Rin$4F8h{R`HxT2z`uc&aMXIQOvwgA5%{&) +zFW52MiN!$!EXgx}Px~e1!EMp;#&kY65oDho95j~!qD%YJr`+aK4jCJ4UJ^;q>w@Lf +zvDfg|M`S^@DGxu+7aR3Cx#;%?advj&1~L-m +zJqCP9&TW3migV*`Z$#)Qa>3>Jf)g9D6Ki28P@iX(uso)hic8Dp1F< +zeF;(n8Po8A*~^T{De(J)Z2nqLl@Vv3yoSlGwq0aeOg4ymI(KIkTeur-=J-yp9z?qe)it6gq-wl@I +z0D-_I{|T<5kwD9uH3yf1GWXp5*8eOgJf*q0IRoK|+r{}Fug&0WpNDKMTC@(Xc)9K8 +zy`lByMn!1fnY)1KYP(0Je1)c~WilUuh<&Q8^OE?L9Q^xK*Y@M$`6D6TDCZ^@l8{|} +zxmmNw)mng$hYBii+&ZqedxWT0dnV#LG4zC%+kzcK+-??vEHT>Q-T8zu|s_1IbA#OV)^+1pg1OmmZn` + +literal 0 +HcmV?d00001 + diff --git a/patches/server-unmapped/0001/0684-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch b/patches/server-unmapped/0001/0684-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch new file mode 100644 index 0000000000..852ff8decb --- /dev/null +++ b/patches/server-unmapped/0001/0684-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Tue, 2 Feb 2021 09:17:59 +0100 +Subject: [PATCH] stop firing pressure plate EntityInteractEvent for ignored + entities + + +diff --git a/src/main/java/net/minecraft/world/level/block/BlockPressurePlateBinary.java b/src/main/java/net/minecraft/world/level/block/BlockPressurePlateBinary.java +index 4c4c8b23c39d26c646b1950023a20446ac798c6f..fba720bf2349a69d0f93642eea4e77063f83380c 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockPressurePlateBinary.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockPressurePlateBinary.java +@@ -82,6 +82,7 @@ public class BlockPressurePlateBinary extends BlockPressurePlateAbstract { + + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); ++ if (entity.isIgnoreBlockTrigger()) continue; // Paper - don't call event for ignored entities + + // CraftBukkit start - Call interact event when turning on a pressure plate + if (this.getPower(world.getType(blockposition)) == 0) { diff --git a/patches/server-unmapped/0001/0685-fix-converting-txt-to-json-file.patch b/patches/server-unmapped/0001/0685-fix-converting-txt-to-json-file.patch new file mode 100644 index 0000000000..7dfa73f6ba --- /dev/null +++ b/patches/server-unmapped/0001/0685-fix-converting-txt-to-json-file.patch @@ -0,0 +1,74 @@ +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 b13e6f9923a9c5703f4eaeab2d0c112e4726b496..a762cf4c4a52bcbc8dbfd60b3ad7fef5489ba5c5 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.a(dedicatedserverproperties.viewDistance); + super.setHasWhitelist((Boolean) dedicatedserverproperties.whiteList.get()); ++ // Paper start - moved from constructor ++ } ++ @Override ++ public void loadAndSaveFiles() { ++ // Paper end + this.y(); + this.w(); + this.x(); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index c4df472050622eb2469b2ddb4d2ed917994f6e95..52bb528e75eb43156ee2bf19877bc051a35bb6e3 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -198,6 +198,12 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + org.spigotmc.SpigotConfig.init((java.io.File) options.valueOf("spigot-settings")); + org.spigotmc.SpigotConfig.registerCommands(); + // Spigot end ++ // Paper start - moved up to right after PlayerList creation but before file load/save ++ if (this.convertNames()) { ++ this.getUserCache().save(false); // Paper ++ } ++ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames ++ // Paper end + // Paper start + try { + com.destroystokyo.paper.PaperConfig.init((java.io.File) options.valueOf("paper-settings")); +@@ -260,10 +266,6 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); + } + +- if (this.convertNames()) { +- this.getUserCache().b(false); // Paper +- } +- + if (!NameReferencingFileConverter.e(this)) { + return false; + } else { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 24b3a893a2b76a4ecfbc6b2cc1eac242e5c6e9d6..92ed4938d5fe6b76e3a9ac5491d6e9c004ade843 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -175,6 +175,7 @@ public abstract class PlayerList { + this.maxPlayers = i; + this.playerFileData = worldnbtstorage; + } ++ abstract public void loadAndSaveFiles(); // Paper - moved from DedicatedPlayerList constructor + + public void a(NetworkManager networkmanager, EntityPlayer entityplayer) { + EntityPlayer prev = pendingPlayers.put(entityplayer.getUniqueID(), entityplayer);// Paper +diff --git a/src/main/java/net/minecraft/server/players/UserCache.java b/src/main/java/net/minecraft/server/players/UserCache.java +index e17927ecc3ad3e27e436082ac94e3772d7311725..e813d4dd1a46734d16b42905808caa889811afcc 100644 +--- a/src/main/java/net/minecraft/server/players/UserCache.java ++++ b/src/main/java/net/minecraft/server/players/UserCache.java +@@ -242,6 +242,7 @@ public class UserCache { + return arraylist; + } + ++ public void save(boolean asyncSave) { b(asyncSave); } // Paper - OBFHELPER + public void b(boolean asyncSave) { // Paper + JsonArray jsonarray = new JsonArray(); + DateFormat dateformat = e(); diff --git a/patches/server-unmapped/0001/0686-Add-worldborder-events.patch b/patches/server-unmapped/0001/0686-Add-worldborder-events.patch new file mode 100644 index 0000000000..49bf0c1ca5 --- /dev/null +++ b/patches/server-unmapped/0001/0686-Add-worldborder-events.patch @@ -0,0 +1,122 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jan 2021 22:40:34 -0800 +Subject: [PATCH] Add worldborder events + + +diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +index bedaa9dd6390e81df5872c2dd6e202a038367bf6..3c25021835d6d8fd112fc89636616bfd744e7f1a 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -14,6 +14,9 @@ import net.minecraft.world.phys.AxisAlignedBB; + import net.minecraft.world.phys.shapes.OperatorBoolean; + import net.minecraft.world.phys.shapes.VoxelShape; + import net.minecraft.world.phys.shapes.VoxelShapes; ++import io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent; // Paper ++import io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent; // Paper ++import io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent; // Paper + + public class WorldBorder { + +@@ -102,15 +105,19 @@ public class WorldBorder { + } + + public void setCenter(double d0, double d1) { +- this.g = d0; +- this.h = d1; ++ // Paper start ++ WorldBorderCenterChangeEvent event = new WorldBorderCenterChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), new org.bukkit.Location(world.getWorld(), this.getCenterX(), 0, this.getCenterZ()), new org.bukkit.Location(world.getWorld(), d0, 0, d1)); ++ if (!event.callEvent()) return; ++ this.g = event.getNewCenter().getX(); ++ this.h = event.getNewCenter().getZ(); ++ // Paper end + this.j.k(); + Iterator iterator = this.l().iterator(); + + while (iterator.hasNext()) { + IWorldBorderListener iworldborderlistener = (IWorldBorderListener) iterator.next(); + +- iworldborderlistener.a(this, d0, d1); ++ iworldborderlistener.a(this, event.getNewCenter().getX(), event.getNewCenter().getZ()); // Paper + } + + } +@@ -128,25 +135,43 @@ public class WorldBorder { + } + + public void setSize(double d0) { +- this.j = new WorldBorder.d(d0); ++ // Paper start ++ WorldBorderBoundsChangeEvent event = new WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE, getSize(), d0, 0); ++ if (!event.callEvent()) return; ++ if (event.getType() == WorldBorderBoundsChangeEvent.Type.STARTED_MOVE && event.getDuration() > 0) { // If changed to a timed transition ++ transitionSizeBetween(event.getOldSize(), event.getNewSize(), event.getDuration()); ++ return; ++ } ++ this.j = new WorldBorder.d(event.getNewSize()); ++ // Paper end + Iterator iterator = this.l().iterator(); + + while (iterator.hasNext()) { + IWorldBorderListener iworldborderlistener = (IWorldBorderListener) iterator.next(); + +- iworldborderlistener.a(this, d0); ++ iworldborderlistener.a(this, event.getNewSize()); // Paper + } + + } + + public void transitionSizeBetween(double d0, double d1, long i) { +- this.j = (WorldBorder.a) (d0 == d1 ? new WorldBorder.d(d1) : new WorldBorder.b(d0, d1, i)); ++ // Paper start ++ WorldBorderBoundsChangeEvent.Type type; ++ if (d0 == d1) { // new size = old size ++ type = WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE; // Use INSTANT_MOVE because below it creates a Static border if they are equal. ++ } else { ++ type = WorldBorderBoundsChangeEvent.Type.STARTED_MOVE; ++ } ++ WorldBorderBoundsChangeEvent event = new WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), type, d0, d1, i); ++ if (!event.callEvent()) return; ++ this.j = (WorldBorder.a) (d0 == event.getNewSize() ? new WorldBorder.d(event.getNewSize()) : new WorldBorder.b(d0, event.getNewSize(), event.getDuration())); ++ // Paper end + Iterator iterator = this.l().iterator(); + + while (iterator.hasNext()) { + IWorldBorderListener iworldborderlistener = (IWorldBorderListener) iterator.next(); + +- iworldborderlistener.a(this, d0, d1, i); ++ iworldborderlistener.a(this, d0, event.getNewSize(), event.getDuration()); // Paper + } + + } +@@ -434,11 +459,11 @@ public class WorldBorder { + + class b implements WorldBorder.a { + +- private final double b; +- private final double c; ++ private final double b; public final double getOldSize() { return this.b; } // Paper - OBFHELPER ++ private final double c; public final double getNewSize() { return this.c; } // Paper - OBFHELPER + private final long d; + private final long e; +- private final double f; ++ private final double f; public final double getDuration() { return this.f; } // Paper - OBFHELPER + + private b(double d0, double d1, long i) { + this.b = d0; +@@ -493,6 +518,7 @@ public class WorldBorder { + + @Override + public WorldBorder.a l() { ++ if (this.getLerpTimeRemaining() <= 0L) new WorldBorderBoundsChangeFinishEvent(world.getWorld(), world.getWorld().getWorldBorder(), getOldSize(), getNewSize(), getDuration()).callEvent(); // Paper + return (WorldBorder.a) (this.g() <= 0L ? WorldBorder.this.new d(this.c) : this); + } + +@@ -514,6 +540,7 @@ public class WorldBorder { + + double e(); + ++ default long getLerpTimeRemaining() { return g(); } // Paper - OBFHELPER + long g(); + + double h(); diff --git a/patches/server-unmapped/0001/0687-added-PlayerNameEntityEvent.patch b/patches/server-unmapped/0001/0687-added-PlayerNameEntityEvent.patch new file mode 100644 index 0000000000..c4ab87d789 --- /dev/null +++ b/patches/server-unmapped/0001/0687-added-PlayerNameEntityEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 5 Jul 2020 00:33:54 -0700 +Subject: [PATCH] added PlayerNameEntityEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ItemNameTag.java b/src/main/java/net/minecraft/world/item/ItemNameTag.java +index 140a865f4f8fb3e4f787cf8d334d20fac6cb5eef..b9b2b27e534ba87a1aae3c521f393a066a18a199 100644 +--- a/src/main/java/net/minecraft/world/item/ItemNameTag.java ++++ b/src/main/java/net/minecraft/world/item/ItemNameTag.java +@@ -1,11 +1,17 @@ + package net.minecraft.world.item; + ++import net.minecraft.server.level.EntityPlayer; + import net.minecraft.world.EnumHand; + import net.minecraft.world.EnumInteractionResult; + import net.minecraft.world.entity.EntityInsentient; + import net.minecraft.world.entity.EntityLiving; + import net.minecraft.world.entity.player.EntityHuman; + ++// Paper start ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.event.player.PlayerNameEntityEvent; ++// Paper end ++ + public class ItemNameTag extends Item { + + public ItemNameTag(Item.Info item_info) { +@@ -16,11 +22,15 @@ public class ItemNameTag extends Item { + public EnumInteractionResult a(ItemStack itemstack, EntityHuman entityhuman, EntityLiving entityliving, EnumHand enumhand) { + if (itemstack.hasName() && !(entityliving instanceof EntityHuman)) { + if (!entityhuman.world.isClientSide && entityliving.isAlive()) { +- entityliving.setCustomName(itemstack.getName()); +- if (entityliving instanceof EntityInsentient) { +- ((EntityInsentient) entityliving).setPersistent(); ++ // Paper start ++ PlayerNameEntityEvent event = new PlayerNameEntityEvent(((EntityPlayer) entityhuman).getBukkitEntity(), entityliving.getBukkitLivingEntity(), PaperAdventure.asAdventure(itemstack.getName()), true); ++ if (!event.callEvent()) return EnumInteractionResult.PASS; ++ EntityLiving newEntityLiving = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); ++ newEntityLiving.setCustomName(event.getName() != null ? PaperAdventure.asVanilla(event.getName()) : null); ++ if (event.isPersistent() && newEntityLiving instanceof EntityInsentient) { ++ ((EntityInsentient) newEntityLiving).setPersistent(); + } +- ++ // Paper end + itemstack.subtract(1); + } + diff --git a/patches/server-unmapped/0001/0688-Prevent-grindstones-from-overstacking-items.patch b/patches/server-unmapped/0001/0688-Prevent-grindstones-from-overstacking-items.patch new file mode 100644 index 0000000000..7eba41fbef --- /dev/null +++ b/patches/server-unmapped/0001/0688-Prevent-grindstones-from-overstacking-items.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Tue, 16 Feb 2021 21:37:51 -0600 +Subject: [PATCH] Prevent grindstones from overstacking items + + +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java b/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java +index 1d5dcbbd3870eb8e1013a97f6ce882bfc096bf95..a841c47c25287080738b04352ea4e8eaa77dacdd 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerGrindstone.java +@@ -196,13 +196,13 @@ public class ContainerGrindstone extends Container { + i = Math.max(item.getMaxDurability() - l, 0); + itemstack2 = this.b(itemstack, itemstack1); + if (!itemstack2.e()) { +- if (!ItemStack.matches(itemstack, itemstack1)) { ++ if (!ItemStack.matches(itemstack, itemstack1) || itemstack2.getMaxStackSize() == 1) { // Paper - add max stack size check + this.resultInventory.setItem(0, ItemStack.b); + this.c(); + return; + } + +- b0 = 2; ++ b0 = 2; // Paper - the problem line for above change, causing over-stacking + } + } else { + boolean flag3 = !itemstack.isEmpty(); diff --git a/patches/server-unmapped/0001/0689-Add-recipe-to-cook-events.patch b/patches/server-unmapped/0001/0689-Add-recipe-to-cook-events.patch new file mode 100644 index 0000000000..41d4ad4460 --- /dev/null +++ b/patches/server-unmapped/0001/0689-Add-recipe-to-cook-events.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> +Date: Wed, 6 Jan 2021 12:04:03 -0800 +Subject: [PATCH] Add recipe to cook events + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java +index 62a19f39405cff27f34a3b98fb9310b1c9c27563..08759f461ec947c0d5655557f49d8717afee6f00 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityCampfire.java +@@ -74,7 +74,10 @@ public class TileEntityCampfire extends TileEntity implements Clearable, ITickab + + if (this.cookingTimes[i] >= this.cookingTotalTimes[i]) { + InventorySubcontainer inventorysubcontainer = new InventorySubcontainer(new ItemStack[]{itemstack}); +- ItemStack itemstack1 = (ItemStack) this.world.getCraftingManager().craft(Recipes.CAMPFIRE_COOKING, inventorysubcontainer, this.world).map((recipecampfire) -> { ++ // Paper start ++ Optional recipe = this.world.getCraftingManager().craft(Recipes.CAMPFIRE_COOKING, inventorysubcontainer, this.world); ++ ItemStack itemstack1 = (ItemStack) recipe.map((recipecampfire) -> { ++ // Paper end + return recipecampfire.a(inventorysubcontainer); + }).orElse(itemstack); + BlockPosition blockposition = this.getPosition(); +@@ -83,7 +86,7 @@ public class TileEntityCampfire extends TileEntity implements Clearable, ITickab + CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); + org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); + +- BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(this.world, this.position), source, result); ++ BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(this.world, this.position), source, result, (org.bukkit.inventory.CookingRecipe) recipe.map(RecipeCampfire::toBukkitRecipe).orElse(null)); // Paper + this.world.getServer().getPluginManager().callEvent(blockCookEvent); + + if (blockCookEvent.isCancelled()) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +index 54316a8079b4331a48cac3c43f3f8c506a4af091..1997139fb87dc1947acfdf02e1f116577c3fa943 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +@@ -395,7 +395,7 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); + org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); + +- FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(this.world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), source, result); ++ FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(this.world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), source, result, (org.bukkit.inventory.CookingRecipe) irecipe.toBukkitRecipe()); // Paper + this.world.getServer().getPluginManager().callEvent(furnaceSmeltEvent); + + if (furnaceSmeltEvent.isCancelled()) { diff --git a/patches/server-unmapped/0001/0690-Add-Block-isValidTool.patch b/patches/server-unmapped/0001/0690-Add-Block-isValidTool.patch new file mode 100644 index 0000000000..6cfa745ab3 --- /dev/null +++ b/patches/server-unmapped/0001/0690-Add-Block-isValidTool.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 6 Jul 2020 12:44:31 -0700 +Subject: [PATCH] Add Block#isValidTool + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index ed1c92d9f2770f7d0503c6facebc51ddcbdf75cf..0006b3cad5fe46e50b0efeae99102f9d80276d61 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -806,5 +806,9 @@ public class CraftBlock implements Block { + } + return speed; + } ++ ++ public boolean isValidTool(ItemStack itemStack) { ++ return getDrops(itemStack).size() != 0; ++ } + // Paper end + } diff --git a/patches/server-unmapped/0001/0691-Allow-using-signs-inside-spawn-protection.patch b/patches/server-unmapped/0001/0691-Allow-using-signs-inside-spawn-protection.patch new file mode 100644 index 0000000000..070f1d7f72 --- /dev/null +++ b/patches/server-unmapped/0001/0691-Allow-using-signs-inside-spawn-protection.patch @@ -0,0 +1,41 @@ +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/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 3e6132211912d29e34c94042b0819f11a3bd123e..921253a06daa414aed7dc6824effc65db09ea7a5 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -810,4 +810,9 @@ public class PaperWorldConfig { + fixWitherTargetingBug = getBoolean("fix-wither-targeting-bug", false); + log("Withers properly target players: " + fixWitherTargetingBug); + } ++ ++ public boolean allowUsingSignsInsideSpawnProtection = false; ++ private void allowUsingSignsInsideSpawnProtection() { ++ allowUsingSignsInsideSpawnProtection = getBoolean("allow-using-signs-inside-spawn-protection", allowUsingSignsInsideSpawnProtection); ++ } + } +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 358d1095b219fce6b308ec0362f22db7cfc85251..c6a65467d2d096d471ce5c4d761dc69d60644b75 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -143,6 +143,7 @@ import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.IWorldReader; + import net.minecraft.world.level.World; + import net.minecraft.world.level.block.BlockCommand; ++import net.minecraft.world.level.block.BlockSign; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.entity.TileEntity; + import net.minecraft.world.level.block.entity.TileEntityCommand; +@@ -1691,7 +1692,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + + this.player.resetIdleTimer(); + if (blockposition.getY() < this.minecraftServer.getMaxBuildHeight()) { +- if (this.teleportPos == null && this.player.h((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.a((EntityHuman) this.player, blockposition)) { ++ if (this.teleportPos == null && this.player.h((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && (worldserver.a((EntityHuman) this.player, blockposition) || (worldserver.paperConfig.allowUsingSignsInsideSpawnProtection && worldserver.getType(blockposition).getBlock() instanceof BlockSign))) { // Paper + // CraftBukkit start - Check if we can actually do something over this large a distance + // Paper - move check up + this.player.clearActiveItem(); // SPIGOT-4706 diff --git a/patches/server-unmapped/0001/0692-Implement-Keyed-on-World.patch b/patches/server-unmapped/0001/0692-Implement-Keyed-on-World.patch new file mode 100644 index 0000000000..138f3341bb --- /dev/null +++ b/patches/server-unmapped/0001/0692-Implement-Keyed-on-World.patch @@ -0,0 +1,110 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 6 Jan 2021 00:34:04 -0800 +Subject: [PATCH] Implement Keyed on World + + +diff --git a/src/main/java/net/minecraft/core/IRegistry.java b/src/main/java/net/minecraft/core/IRegistry.java +index 3e9ebeffdf66f8a959630b344149d17137c6901c..4f04d8081912e0fe771f0db9e086c789328f246f 100644 +--- a/src/main/java/net/minecraft/core/IRegistry.java ++++ b/src/main/java/net/minecraft/core/IRegistry.java +@@ -130,7 +130,7 @@ public abstract class IRegistry implements Codec, Keyable, Registry { + public static final ResourceKey> I = a("loot_function_type"); + public static final ResourceKey> J = a("loot_condition_type"); + public static final ResourceKey> K = a("dimension_type"); +- public static final ResourceKey> L = a("dimension"); ++ public static final ResourceKey> L = a("dimension"); public static final ResourceKey> getWorldRegistry() { return L; } // Paper - OBFHELPER + public static final ResourceKey> M = a("dimension"); + public static final IRegistry SOUND_EVENT = a(IRegistry.g, () -> { + return SoundEffects.ENTITY_ITEM_PICKUP; +@@ -339,9 +339,9 @@ public abstract class IRegistry implements Codec, Keyable, Registry { + MinecraftKey minecraftkey = resourcekey.a(); + + IRegistry.a.put(minecraftkey, supplier); +- IRegistryWritable iregistrywritable = IRegistry.e; ++ IRegistryWritable iregistrywritable = IRegistry.e; // Paper - decompile fix + +- return (IRegistryWritable) iregistrywritable.a(resourcekey, (Object) r0, lifecycle); ++ return (R) iregistrywritable.a(resourcekey, (Object) r0, lifecycle); // Paper - decompile fix + } + + protected IRegistry(ResourceKey> resourcekey, Lifecycle lifecycle) { +@@ -428,11 +428,11 @@ public abstract class IRegistry implements Codec, Keyable, Registry { + } + + public static T a(IRegistry iregistry, MinecraftKey minecraftkey, T t0) { +- return ((IRegistryWritable) iregistry).a(ResourceKey.a(iregistry.b, minecraftkey), t0, Lifecycle.stable()); ++ return ((IRegistryWritable) iregistry).a(ResourceKey.a(iregistry.b, minecraftkey), t0, Lifecycle.stable()); // Paper - decompile fix + } + + public static T a(IRegistry iregistry, int i, String s, T t0) { +- return ((IRegistryWritable) iregistry).a(i, ResourceKey.a(iregistry.b, new MinecraftKey(s)), t0, Lifecycle.stable()); ++ return ((IRegistryWritable) iregistry).a(i, ResourceKey.a(iregistry.b, new MinecraftKey(s)), t0, Lifecycle.stable()); // Paper - decompile fix + } + + static { +diff --git a/src/main/java/net/minecraft/resources/ResourceKey.java b/src/main/java/net/minecraft/resources/ResourceKey.java +index 760579921927b4c8b0f20b2611b95fd626e4b27f..3075700dfa992da81b10246fcf7c7ad1115c4ba3 100644 +--- a/src/main/java/net/minecraft/resources/ResourceKey.java ++++ b/src/main/java/net/minecraft/resources/ResourceKey.java +@@ -12,6 +12,7 @@ public class ResourceKey { + private final MinecraftKey b; + private final MinecraftKey c; + ++ public static ResourceKey newResourceKey(ResourceKey> registryKey, MinecraftKey minecraftKey) { return a(registryKey, minecraftKey); } // Paper - OBFHELPER + public static ResourceKey a(ResourceKey> resourcekey, MinecraftKey minecraftkey) { + return a(resourcekey.c, minecraftkey); + } +@@ -41,6 +42,7 @@ public class ResourceKey { + return this.b.equals(resourcekey.a()); + } + ++ public MinecraftKey getLocation() { return a(); } // Paper - OBFHELPER + public MinecraftKey a() { + return this.c; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index cbac3c96c5d3c1551912f5769bfc50d690519495..03b8d67a5f0088c0254b2099f27e8dcae32a6221 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1154,7 +1154,7 @@ public final class CraftServer implements Server { + chunkgenerator = worlddimension.c(); + } + +- ResourceKey worldKey = ResourceKey.a(IRegistry.L, new MinecraftKey(name.toLowerCase(java.util.Locale.ENGLISH))); ++ ResourceKey worldKey = ResourceKey.newResourceKey(IRegistry.getWorldRegistry(), new MinecraftKey(creator.key().getNamespace().toLowerCase(java.util.Locale.ENGLISH), creator.key().getKey().toLowerCase(java.util.Locale.ENGLISH))); // Paper + + WorldServer internal = (WorldServer) new WorldServer(console, console.executorService, worldSession, worlddata, worldKey, dimensionmanager, getServer().worldLoadListenerFactory.create(11), + chunkgenerator, worlddata.getGeneratorSettings().isDebugWorld(), j, creator.environment() == Environment.NORMAL ? list : ImmutableList.of(), true, creator.environment(), generator); +@@ -1245,6 +1245,15 @@ public final class CraftServer implements Server { + return null; + } + ++ // Paper start ++ @Override ++ public World getWorld(NamespacedKey worldKey) { ++ WorldServer worldServer = console.getWorldServer(ResourceKey.newResourceKey(IRegistry.getWorldRegistry(), 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 (getWorld(world.getUID()) != null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index ef353e21f7e04162d886e70012f845334962459b..05098332d83b1abfaa0a6d3bd4a9e801ea90d2ad 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2584,6 +2584,11 @@ public class CraftWorld implements World { + return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); + }, net.minecraft.server.MinecraftServer.getServer()); + } ++ ++ @Override ++ public org.bukkit.NamespacedKey getKey() { ++ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(world.getDimensionKey().getLocation()); ++ } + // Paper end + + // Spigot start diff --git a/patches/server-unmapped/0001/0693-Add-fast-alternative-constructor-for-Vector3f.patch b/patches/server-unmapped/0001/0693-Add-fast-alternative-constructor-for-Vector3f.patch new file mode 100644 index 0000000000..65430ea1e7 --- /dev/null +++ b/patches/server-unmapped/0001/0693-Add-fast-alternative-constructor-for-Vector3f.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Irmo van den Berge +Date: Wed, 10 Mar 2021 21:26:31 +0100 +Subject: [PATCH] Add fast alternative constructor for Vector3f + +Signed-off-by: Irmo van den Berge + +diff --git a/src/main/java/net/minecraft/core/Vector3f.java b/src/main/java/net/minecraft/core/Vector3f.java +index 93590ceb0bbe369a1bda987f0c4c21ea6a3b3a1a..da53e294fa5e28406b25614039db49b48c2dcdac 100644 +--- a/src/main/java/net/minecraft/core/Vector3f.java ++++ b/src/main/java/net/minecraft/core/Vector3f.java +@@ -19,6 +19,18 @@ public class Vector3f { + this(nbttaglist.i(0), nbttaglist.i(1), nbttaglist.i(2)); + } + ++ // Paper start - faster alternative constructor ++ private Vector3f(float x, float y, float z, Void dummy_var) { ++ this.x = x; ++ this.y = y; ++ this.z = z; ++ } ++ ++ public static Vector3f createWithoutValidityChecks(float x, float y, float z) { ++ return new Vector3f(x, y, z, null); ++ } ++ // Paper end ++ + public NBTTagList a() { + NBTTagList nbttaglist = new NBTTagList(); + diff --git a/patches/server-unmapped/0001/0694-Item-Rarity-API.patch b/patches/server-unmapped/0001/0694-Item-Rarity-API.patch new file mode 100644 index 0000000000..a60433f8c3 --- /dev/null +++ b/patches/server-unmapped/0001/0694-Item-Rarity-API.patch @@ -0,0 +1,52 @@ +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 + + +diff --git a/src/main/java/net/minecraft/world/item/Item.java b/src/main/java/net/minecraft/world/item/Item.java +index 5d7c44a53fb98532057b09176677ce0d719b055b..e6a838430084d64326d1042c7b2089f49a24a789 100644 +--- a/src/main/java/net/minecraft/world/item/Item.java ++++ b/src/main/java/net/minecraft/world/item/Item.java +@@ -45,7 +45,7 @@ public class Item implements IMaterial { + protected static final UUID g = UUID.fromString("FA233E1C-4180-4865-B01B-BCCE9785ACA3"); + protected static final Random RANDOM = new Random(); + protected final CreativeModeTab i; +- private final EnumItemRarity a; ++ private final EnumItemRarity a; public final EnumItemRarity getItemRarity() { return a; } // Paper - OBFHELPER + private final int maxStackSize; + private final int durability; + private final boolean d; +@@ -208,6 +208,7 @@ public class Item implements IMaterial { + return itemstack.hasEnchantments(); + } + ++ public EnumItemRarity getItemStackRarity(ItemStack itemStack) { return i(itemStack); } // Paper - OBFHELPER + public EnumItemRarity i(ItemStack itemstack) { + if (!itemstack.hasEnchantments()) { + return this.a; +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index f69b4576f05dbf763e99d5d1cbed069c55c793ed..971877c42f7a46696a389ef7d93f44993c360810 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -467,6 +467,20 @@ public final class CraftMagicNumbers implements UnsafeValues { + return net.minecraft.world.entity.Entity.nextEntityId(); + } + ++ @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.getItemRarity().ordinal()]; ++ } ++ ++ @Override ++ public io.papermc.paper.inventory.ItemRarity getItemStackRarity(org.bukkit.inventory.ItemStack itemStack) { ++ return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getItemStackRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; ++ } ++ + // Paper end + + /** diff --git a/patches/server-unmapped/0001/0695-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch b/patches/server-unmapped/0001/0695-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch new file mode 100644 index 0000000000..61d73b8e95 --- /dev/null +++ b/patches/server-unmapped/0001/0695-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Fri, 19 Mar 2021 16:07:21 -0700 +Subject: [PATCH] Only set despawnTimer for Wandering Traders spawned by + MobSpawnerTrader + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityTypes.java b/src/main/java/net/minecraft/world/entity/EntityTypes.java +index f2cf33d42839710a3bbdf0c8ea0be28af6fcb19d..80c229c1852199fda85c03453d64cae33e413e89 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityTypes.java ++++ b/src/main/java/net/minecraft/world/entity/EntityTypes.java +@@ -319,6 +319,12 @@ public class EntityTypes { + + @Nullable + public T spawnCreature(WorldServer worldserver, @Nullable NBTTagCompound nbttagcompound, @Nullable IChatBaseComponent ichatbasecomponent, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start - add consumer to modify entity before spawn ++ return this.spawnCreature(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1, spawnReason, null); ++ } ++ @Nullable ++ public T spawnCreature(WorldServer worldserver, @Nullable NBTTagCompound nbttagcompound, @Nullable IChatBaseComponent ichatbasecomponent, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason, @Nullable java.util.function.Consumer op) { ++ // Paper end + // Paper start - Call PreCreatureSpawnEvent + org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(EntityTypes.getName(this).getKey()); + if (type != null) { +@@ -334,6 +340,7 @@ public class EntityTypes { + } + // Paper end + T t0 = this.createCreature(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1); ++ if (t0 != null && op != null) op.accept(t0); // Paper + + if (t0 != null) { + worldserver.addAllEntities(t0, spawnReason); +diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerTrader.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerTrader.java +index 4f81a97b1451fec0bb5fd1479acad97846c40c7c..37e1b2bf33510c3603efadf219b462e667f573c2 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillagerTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillagerTrader.java +@@ -62,7 +62,7 @@ public class EntityVillagerTrader extends EntityVillagerAbstract { + public EntityVillagerTrader(EntityTypes entitytypes, World world) { + super(entitytypes, world); + this.attachedToPlayer = true; +- this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader ++ //this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set. + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/npc/MobSpawnerTrader.java b/src/main/java/net/minecraft/world/entity/npc/MobSpawnerTrader.java +index e57938b4591bb103b9dd0d0145a62b5a901f2c63..7c8a2151be8a0f48cba1c15d231d5dbdb500b4d6 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/MobSpawnerTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/MobSpawnerTrader.java +@@ -114,7 +114,7 @@ public class MobSpawnerTrader implements MobSpawner { + return false; + } + +- EntityVillagerTrader entityvillagertrader = (EntityVillagerTrader) EntityTypes.WANDERING_TRADER.spawnCreature(worldserver, (NBTTagCompound) null, (IChatBaseComponent) null, (EntityHuman) null, blockposition2, EnumMobSpawn.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit ++ EntityVillagerTrader entityvillagertrader = EntityTypes.WANDERING_TRADER.spawnCreature(worldserver, null, null, null, blockposition2, EnumMobSpawn.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL, trader -> trader.setDespawnDelay(48000)); // CraftBukkit // Paper - set despawnTimer before spawn events called + + if (entityvillagertrader != null) { + for (int i = 0; i < 2; ++i) { diff --git a/patches/server-unmapped/0001/0696-copy-TESign-isEditable-from-snapshots.patch b/patches/server-unmapped/0001/0696-copy-TESign-isEditable-from-snapshots.patch new file mode 100644 index 0000000000..25bfef8508 --- /dev/null +++ b/patches/server-unmapped/0001/0696-copy-TESign-isEditable-from-snapshots.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 23 Mar 2021 06:43:30 +0000 +Subject: [PATCH] copy TESign#isEditable from snapshots + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +index f4b601277ef75e5bc39d541a0d13c6eed3b1ef2c..f7e2e23e7468928b6bd6961f223c50ca2826a813 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +@@ -110,6 +110,7 @@ public class CraftSign extends CraftBlockEntityState implements + } + // Paper end + } ++ sign.isEditable = getSnapshot().isEditable; // Paper - copy manually + } + + // Paper start diff --git a/patches/server-unmapped/0001/0697-Drop-carried-item-when-player-has-disconnected.patch b/patches/server-unmapped/0001/0697-Drop-carried-item-when-player-has-disconnected.patch new file mode 100644 index 0000000000..035581c26d --- /dev/null +++ b/patches/server-unmapped/0001/0697-Drop-carried-item-when-player-has-disconnected.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dmitry Sidorov +Date: Thu, 4 Feb 2021 20:32:01 +0300 +Subject: [PATCH] Drop carried item when player has disconnected + +Fixes disappearance of held items, when a player gets disconnected and PlayerDropItemEvent is cancelled. +Closes #5036 + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 92ed4938d5fe6b76e3a9ac5491d6e9c004ade843..2df8e914f66176e22aeddf8b94a83af5ea921d88 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -79,6 +79,7 @@ import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityTypes; + import net.minecraft.world.entity.npc.EntityVillagerAbstract; + import net.minecraft.world.entity.player.EntityHuman; ++import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.ChunkCoordIntPair; + import net.minecraft.world.level.EnumGamemode; + import net.minecraft.world.level.GameRules; +@@ -609,6 +610,14 @@ public abstract class PlayerList { + } + // Paper end + ++ // Paper - Drop carried item when player has disconnected ++ if (!entityplayer.inventory.getCarried().isEmpty()) { ++ ItemStack carried = entityplayer.inventory.getCarried(); ++ entityplayer.inventory.setCarried(ItemStack.NULL_ITEM); ++ entityplayer.drop(carried, false); ++ } ++ // Paper end ++ + this.savePlayerFile(entityplayer); + if (entityplayer.isPassenger()) { + Entity entity = entityplayer.getRootVehicle(); diff --git a/patches/server-unmapped/0001/0698-forced-whitelist-use-configurable-kick-message.patch b/patches/server-unmapped/0001/0698-forced-whitelist-use-configurable-kick-message.patch new file mode 100644 index 0000000000..43be5f2fc0 --- /dev/null +++ b/patches/server-unmapped/0001/0698-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 84c3110ea03f9121fc4ab0aaa80ddad5efe28e5c..61712ae515b329a6b85dbe2e5960e4e864dc7731 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2040,7 +2040,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Mon, 29 Mar 2021 09:07:25 +0200 +Subject: [PATCH] Make sure to remove correct TE during TE tick + +This looks like it can cause premature TE removal. + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index 78dcba08d6d796d5d97c8304bf1f1e7d1e650d5d..6581fe0d93a5c2e7b444a44c01726e02d4a28e63 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -895,7 +895,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + //this.tileEntityList.remove(tileentity); // Paper - remove unused list + // Paper - prevent double chunk lookups + Chunk chunk; if ((chunk = this.getChunkIfLoaded(tileentity.getPosition())) != null) { // inlined contents of this.isLoaded(BlockPosition). Reuse the returned chunk instead of looking it up again +- chunk.removeTileEntity(tileentity.getPosition()); ++ chunk.removeTileEntity(tileentity.getPosition(), tileentity); // Paper - remove correct TE + } + // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +index 34a9f7b2f998f77b1279516cd09397ab6c2ac1cc..0727b12b5ff146b4efa9204bf4f495f2f1aa20b9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java +@@ -819,10 +819,18 @@ public class Chunk implements IChunkAccess { + + @Override + public void removeTileEntity(BlockPosition blockposition) { ++ // Paper start - remove correct TE ++ removeTileEntity(blockposition, null); ++ } ++ public void removeTileEntity(BlockPosition blockposition, TileEntity match) { ++ // Paper end + if (this.loaded || this.world.s_()) { +- TileEntity tileentity = (TileEntity) this.tileEntities.remove(blockposition); ++ // Paper start ++ TileEntity tileentity = (TileEntity) this.tileEntities.get(blockposition); + +- if (tileentity != null) { ++ if (tileentity != null && (match == null || match == tileentity)) { ++ this.tileEntities.remove(blockposition); ++ // Paper end + tileentity.al_(); + } + } diff --git a/patches/server-unmapped/0001/0700-Don-t-ignore-result-of-PlayerEditBookEvent.patch b/patches/server-unmapped/0001/0700-Don-t-ignore-result-of-PlayerEditBookEvent.patch new file mode 100644 index 0000000000..cc90371c8f --- /dev/null +++ b/patches/server-unmapped/0001/0700-Don-t-ignore-result-of-PlayerEditBookEvent.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +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/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index c6a65467d2d096d471ce5c4d761dc69d60644b75..6ad075907d56a8f41ca3a7b82ff90a6d3ad9f1d4 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1146,7 +1146,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + list.stream().map(NBTTagString::a).forEach(nbttaglist::add); + ItemStack old = itemstack.cloneItemStack(); // CraftBukkit + itemstack.a("pages", (NBTBase) nbttaglist); +- CraftEventFactory.handleEditBookEvent(player, i, old, itemstack); // CraftBukkit ++ this.player.inventory.setItem(i, CraftEventFactory.handleEditBookEvent(player, i, old, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) + } + } + diff --git a/patches/server-unmapped/0001/0701-Expose-protocol-version.patch b/patches/server-unmapped/0001/0701-Expose-protocol-version.patch new file mode 100644 index 0000000000..9fb1e172db --- /dev/null +++ b/patches/server-unmapped/0001/0701-Expose-protocol-version.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KennyTV +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 971877c42f7a46696a389ef7d93f44993c360810..6141e86278d876e42dbed6e8f2275280babcef77 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -481,6 +481,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getItemStackRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; + } + ++ @Override ++ public int getProtocolVersion() { ++ return net.minecraft.SharedConstants.getGameVersion().getProtocolVersion(); ++ } ++ + // Paper end + + /** diff --git a/patches/server-unmapped/0001/0702-Enhance-console-tab-completions-for-brigadier-comman.patch b/patches/server-unmapped/0001/0702-Enhance-console-tab-completions-for-brigadier-comman.patch new file mode 100644 index 0000000000..6a4e84db96 --- /dev/null +++ b/patches/server-unmapped/0001/0702-Enhance-console-tab-completions-for-brigadier-comman.patch @@ -0,0 +1,337 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +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/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index c56e7fb18f9a56c8025eb70a524f028b5942da37..efc1e42d606e1c9feb1a4871c0714933ae92a1b2 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -479,4 +479,11 @@ public class PaperConfig { + private static void fixEntityPositionDesync() { + fixEntityPositionDesync = getBoolean("settings.fix-entity-position-desync", fixEntityPositionDesync); + } ++ ++ public static boolean enableBrigadierConsoleHighlighting = true; ++ public static boolean enableBrigadierConsoleCompletions = true; ++ private static void consoleSettings() { ++ enableBrigadierConsoleHighlighting = getBoolean("settings.console.enable-brigadier-highlighting", enableBrigadierConsoleHighlighting); ++ enableBrigadierConsoleCompletions = getBoolean("settings.console.enable-brigadier-completions", enableBrigadierConsoleCompletions); ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +index 89eeb9d202405747409e65fcf226d95379987e29..ad87b575a0261200b280884e054a59e3ce59c41c 100644 +--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java ++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +@@ -1,5 +1,7 @@ + package com.destroystokyo.paper.console; + ++import com.destroystokyo.paper.PaperConfig; ++import io.papermc.paper.console.BrigadierCommandHighlighter; + import net.minecraft.server.dedicated.DedicatedServer; + import net.minecrell.terminalconsole.SimpleTerminalConsole; + import org.bukkit.craftbukkit.command.ConsoleCommandCompleter; +@@ -16,11 +18,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 (PaperConfig.enableBrigadierConsoleHighlighting) { ++ builder.highlighter(new BrigadierCommandHighlighter(this.server, this.server.getServerCommandListener())); ++ } ++ 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..1b2681e2c0a7c5f7b386e861fecc3002782a09a5 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java +@@ -0,0 +1,120 @@ ++package io.papermc.paper.console; ++ ++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 net.kyori.adventure.text.Component; ++import net.minecraft.commands.CommandListenerWrapper; ++import net.minecraft.network.chat.ChatComponentUtils; ++import net.minecraft.server.dedicated.DedicatedServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.jline.reader.Candidate; ++import org.jline.reader.LineReader; ++import org.jline.reader.ParsedLine; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++ ++public final class BrigadierCommandCompleter { ++ private final CommandListenerWrapper commandSourceStack; ++ private final DedicatedServer server; ++ ++ public BrigadierCommandCompleter(final @NonNull DedicatedServer server, final @NonNull CommandListenerWrapper commandSourceStack) { ++ this.server = server; ++ this.commandSourceStack = commandSourceStack; ++ } ++ ++ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List candidates, final @NonNull List stringCompletions) { ++ if (!com.destroystokyo.paper.PaperConfig.enableBrigadierConsoleCompletions) { ++ this.addCandidates(candidates, Collections.emptyList(), stringCompletions); ++ return; ++ } ++ final CommandDispatcher dispatcher = this.server.getCommandDispatcher().dispatcher(); ++ final ParseResults results = dispatcher.parse(prepareStringReader(line.line()), this.commandSourceStack); ++ this.addCandidates( ++ candidates, ++ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(), ++ stringCompletions ++ ); ++ } ++ ++ private void addCandidates( ++ final @NonNull List candidates, ++ final @NonNull List brigSuggestions, ++ final @NonNull List stringSuggestions ++ ) { ++ final List completions = new ArrayList<>(); ++ brigSuggestions.forEach(it -> completions.add(toCompletion(it))); ++ for (final String string : stringSuggestions) { ++ if (string.isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(string))) { ++ continue; ++ } ++ completions.add(completion(string)); ++ } ++ for (final Completion completion : completions) { ++ if (completion.completion().isEmpty()) { ++ continue; ++ } ++ candidates.add(toCandidate(completion)); ++ } ++ } ++ ++ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) { ++ final String suggestionText = completion.completion(); ++ final String suggestionTooltip = PaperAdventure.PLAIN.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(ChatComponentUtils.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; ++ } ++ ++ static final class Completion { ++ private final String completion; ++ private final Component tooltip; ++ ++ Completion(final @NonNull String completion, final @Nullable Component tooltip) { ++ this.completion = completion; ++ this.tooltip = tooltip; ++ } ++ ++ @NonNull String completion() { ++ return this.completion; ++ } ++ ++ @Nullable Component tooltip() { ++ return this.tooltip; ++ } ++ } ++ ++ static @NonNull Completion completion(final @NonNull String completion) { ++ return new Completion(completion, null); ++ } ++ ++ static @NonNull Completion completion(final @NonNull String completion, final @Nullable Component tooltip) { ++ return new Completion(completion, tooltip); ++ } ++} +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..d51d20a6d1c0c956cdf425503a6c1401acd9c854 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java +@@ -0,0 +1,57 @@ ++package io.papermc.paper.console; ++ ++import com.mojang.brigadier.ParseResults; ++import com.mojang.brigadier.context.ParsedCommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import net.minecraft.commands.CommandListenerWrapper; ++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 CommandListenerWrapper commandSourceStack; ++ private final DedicatedServer server; ++ ++ public BrigadierCommandHighlighter(final @NonNull DedicatedServer server, final @NonNull CommandListenerWrapper commandSourceStack) { ++ this.server = server; ++ this.commandSourceStack = commandSourceStack; ++ } ++ ++ @Override ++ public AttributedString highlight(final @NonNull LineReader reader, final @NonNull String buffer) { ++ final AttributedStringBuilder builder = new AttributedStringBuilder(); ++ final ParseResults results = this.server.getCommandDispatcher().dispatcher().parse(BrigadierCommandCompleter.prepareStringReader(buffer), this.commandSourceStack); ++ 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(); ++ } ++} +diff --git a/src/main/java/net/minecraft/commands/CommandDispatcher.java b/src/main/java/net/minecraft/commands/CommandDispatcher.java +index a70e0761aeddee8fafff971b5cbd0422ab560fb5..988d1c9e9f4f29325043eb083123d12dd5f8081d 100644 +--- a/src/main/java/net/minecraft/commands/CommandDispatcher.java ++++ b/src/main/java/net/minecraft/commands/CommandDispatcher.java +@@ -439,7 +439,7 @@ public class CommandDispatcher { + }; + } + +- public com.mojang.brigadier.CommandDispatcher a() { ++ public com.mojang.brigadier.CommandDispatcher a() { return this.dispatcher(); } public com.mojang.brigadier.CommandDispatcher dispatcher() { // Paper - OBFHELPER + return this.b; + } + +diff --git a/src/main/java/net/minecraft/network/chat/ChatComponentUtils.java b/src/main/java/net/minecraft/network/chat/ChatComponentUtils.java +index b00e5d811ddfa12937f57bac4debb2fdd057d6e1..d14e4bf09bc43e78a9da07ea062ed886d27c7cc0 100644 +--- a/src/main/java/net/minecraft/network/chat/ChatComponentUtils.java ++++ b/src/main/java/net/minecraft/network/chat/ChatComponentUtils.java +@@ -90,7 +90,7 @@ public class ChatComponentUtils { + ChatComponentText chatcomponenttext = new ChatComponentText(""); + boolean flag = true; + +- for (Iterator iterator = collection.iterator(); iterator.hasNext(); flag = false) { ++ for (Iterator iterator = collection.iterator(); iterator.hasNext(); flag = false) { // Paper - decompile fix + T t0 = iterator.next(); + + if (!flag) { +@@ -108,7 +108,7 @@ public class ChatComponentUtils { + return new ChatMessage("chat.square_brackets", new Object[]{ichatbasecomponent}); + } + +- public static IChatBaseComponent a(Message message) { ++ public static IChatBaseComponent a(Message message) { return fromMessage(message); } public static IChatBaseComponent fromMessage(final @org.checkerframework.checker.nullness.qual.NonNull Message message) { // Paper - OBFHELPER + return (IChatBaseComponent) (message instanceof IChatBaseComponent ? (IChatBaseComponent) message : new ChatComponentText(message.getString())); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +index c5e00bd9e2790992202aadf8eec2002fc88c78f1..bb837136285737862aa0b0f3d7fbe834bd69f741 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 + + public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer + this.server = server; ++ this.brigadierCompleter = new io.papermc.paper.console.BrigadierCommandCompleter(this.server, this.server.getServerCommandListener()); // Paper + } + + // Paper start - Change method signature for JLine update +@@ -55,9 +57,10 @@ public class ConsoleCommandCompleter implements Completer { + } + } + +- if (!completions.isEmpty()) { ++ if (false && !completions.isEmpty()) { + candidates.addAll(completions.stream().map(Candidate::new).collect(java.util.stream.Collectors.toList())); + } ++ this.addCompletions(reader, line, candidates, completions); + return; + } + +@@ -77,10 +80,12 @@ public class ConsoleCommandCompleter implements Completer { + try { + List offers = waitable.get(); + if (offers == null) { ++ this.addCompletions(reader, line, candidates, Collections.emptyList()); // Paper + return; // Paper - Method returns void + } + + // Paper start - JLine update ++ /* + for (String completion : offers) { + if (completion.isEmpty()) { + continue; +@@ -88,6 +93,8 @@ public class ConsoleCommandCompleter implements Completer { + + candidates.add(new Candidate(completion)); + } ++ */ ++ this.addCompletions(reader, line, candidates, offers); + // Paper end + + // Paper start - JLine handles cursor now +@@ -106,4 +113,10 @@ public class ConsoleCommandCompleter implements Completer { + Thread.currentThread().interrupt(); + } + } ++ ++ // Paper start ++ private void addCompletions(final LineReader reader, final ParsedLine line, final List candidates, final List stringCompletions) { ++ this.brigadierCompleter.complete(reader, line, candidates, stringCompletions); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0703-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/patches/server-unmapped/0001/0703-Fix-PlayerItemConsumeEvent-cancelling-properly.patch new file mode 100644 index 0000000000..d9657ceefb --- /dev/null +++ b/patches/server-unmapped/0001/0703-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/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java +index 21341eeb8148be119fbc1dd370c1beaf70a319e0..2537c9fcf155253da53ada3829c3caca765f35f4 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java ++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java +@@ -3351,6 +3351,7 @@ public abstract class EntityLiving extends Entity { + world.getServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { ++ this.clearActiveItem(); // Paper - event is using an item, clear active item to reset its use + // Update client + ((EntityPlayer) this).getBukkitEntity().updateInventory(); + ((EntityPlayer) this).getBukkitEntity().updateScaledHealth(); diff --git a/patches/server-unmapped/0001/0704-Validate-bungee-forwarded-hostname.patch b/patches/server-unmapped/0001/0704-Validate-bungee-forwarded-hostname.patch new file mode 100644 index 0000000000..dcf77570a9 --- /dev/null +++ b/patches/server-unmapped/0001/0704-Validate-bungee-forwarded-hostname.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 18 Apr 2021 21:27:01 +0100 +Subject: [PATCH] Validate bungee forwarded hostname + +Seriously, fix your firewalls. -.- + +diff --git a/src/main/java/net/minecraft/server/network/HandshakeListener.java b/src/main/java/net/minecraft/server/network/HandshakeListener.java +index b97d289afdff81d9959e238639f4e3e186f8e9c8..423696df365ceb20b673a87714b753d7b3b7a3af 100644 +--- a/src/main/java/net/minecraft/server/network/HandshakeListener.java ++++ b/src/main/java/net/minecraft/server/network/HandshakeListener.java +@@ -1,5 +1,8 @@ + package net.minecraft.server.network; + ++import com.google.common.net.InetAddresses; ++import com.google.common.net.InternetDomainName; ++ + import net.minecraft.SharedConstants; + import net.minecraft.network.EnumProtocol; + import net.minecraft.network.NetworkManager; +@@ -26,6 +29,7 @@ public class HandshakeListener implements PacketHandshakingInListener { + private static final IChatBaseComponent a = new ChatComponentText("Ignoring status request"); + private final MinecraftServer b; + private final NetworkManager c; final NetworkManager getNetworkManager() { return this.c; } // Paper - OBFHELPER ++ private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper + + public HandshakeListener(MinecraftServer minecraftserver, NetworkManager networkmanager) { + this.b = minecraftserver; +@@ -114,6 +118,14 @@ public class HandshakeListener implements PacketHandshakingInListener { + //if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! + String[] split = packethandshakinginsetprotocol.hostname.split("\00"); + if ( split.length == 3 || split.length == 4 ) { ++ // Paper start ++ if (!BYPASS_HOSTCHECK && !validate(split[1])) { ++ final ChatMessage message = new ChatMessage("Invalid hostname"); ++ this.getNetworkManager().sendPacket(new PacketLoginOutDisconnect(message)); ++ this.getNetworkManager().close(message); ++ return; ++ } ++ // Paper end + packethandshakinginsetprotocol.hostname = split[0]; + c.socketAddress = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) c.getSocketAddress()).getPort()); + c.spoofedUUID = com.mojang.util.UUIDTypeAdapter.fromString( split[2] ); +@@ -158,4 +170,11 @@ public class HandshakeListener implements PacketHandshakingInListener { + public NetworkManager a() { + return this.c; + } ++ ++ // Paper start - https://stackoverflow.com/questions/9954140/check-if-a-string-is-a-hostname-or-an-ip-address-in-java ++ public static boolean validate(final String hostname) { ++ //noinspection UnstableApiUsage ++ return InetAddresses.isUriInetAddress(hostname); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0705-don-t-throw-when-loading-invalid-TEs.patch b/patches/server-unmapped/0001/0705-don-t-throw-when-loading-invalid-TEs.patch new file mode 100644 index 0000000000..eb77500117 --- /dev/null +++ b/patches/server-unmapped/0001/0705-don-t-throw-when-loading-invalid-TEs.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 20 Apr 2021 01:15:04 +0100 +Subject: [PATCH] don't throw when loading invalid TEs + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +index f1e586754396439dfb70a4d63e3b8b34fb36ebf4..93d02ccb87c17404c55884f52ae40c7b7ddfb103 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntity.java +@@ -2,6 +2,7 @@ package net.minecraft.world.level.block.entity; + + import javax.annotation.Nullable; + import net.minecraft.CrashReportSystemDetails; ++import net.minecraft.ResourceKeyInvalidException; + import net.minecraft.core.BlockPosition; + import net.minecraft.core.IRegistry; + import net.minecraft.nbt.NBTTagCompound; +@@ -133,7 +134,13 @@ public abstract class TileEntity implements net.minecraft.server.KeyedObject { / + public static TileEntity create(IBlockData iblockdata, NBTTagCompound nbttagcompound) { + String s = nbttagcompound.getString("id"); + +- return (TileEntity) IRegistry.BLOCK_ENTITY_TYPE.getOptional(new MinecraftKey(s)).map((tileentitytypes) -> { ++ // Paper ++ MinecraftKey minecraftKey = null; ++ try { ++ minecraftKey = new MinecraftKey(s); ++ } catch (ResourceKeyInvalidException ex) {} ++ // Paper end ++ return (TileEntity) IRegistry.BLOCK_ENTITY_TYPE.getOptional(minecraftKey).map((tileentitytypes) -> { + try { + return tileentitytypes.a(); + } catch (Throwable throwable) { diff --git a/patches/server-unmapped/0001/0706-Set-area-affect-cloud-rotation.patch b/patches/server-unmapped/0001/0706-Set-area-affect-cloud-rotation.patch new file mode 100644 index 0000000000..e13e935029 --- /dev/null +++ b/patches/server-unmapped/0001/0706-Set-area-affect-cloud-rotation.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 5 Apr 2021 16:58:20 -0400 +Subject: [PATCH] Set area affect cloud rotation + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 05098332d83b1abfaa0a6d3bd4a9e801ea90d2ad..6c2a4607028c61e4a01ff200d301878e2d63b456 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1977,6 +1977,7 @@ public class CraftWorld implements World { + entity = EntityTypes.LIGHTNING_BOLT.a(world); + } else if (AreaEffectCloud.class.isAssignableFrom(clazz)) { + entity = new EntityAreaEffectCloud(world, x, y, z); ++ entity.setPositionRotation(x, y, z, yaw, pitch); // Paper - Set area effect cloud Rotation + } else if (EvokerFangs.class.isAssignableFrom(clazz)) { + entity = new EntityEvokerFangs(world, x, y, z, (float) Math.toRadians(yaw), 0, null); + } diff --git a/patches/server-unmapped/0001/0707-add-isDeeplySleeping-to-HumanEntity.patch b/patches/server-unmapped/0001/0707-add-isDeeplySleeping-to-HumanEntity.patch new file mode 100644 index 0000000000..5e1db72d90 --- /dev/null +++ b/patches/server-unmapped/0001/0707-add-isDeeplySleeping-to-HumanEntity.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 8 Apr 2021 17:36:10 -0700 +Subject: [PATCH] add isDeeplySleeping to HumanEntity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index b99423b3b413fc6364c6530a99e3c74dd406e1b4..f2b2db663198037ba4b7942815bfcd5ddd0e2a8d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -123,6 +123,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + } + } + ++ // Paper start ++ @Override ++ public boolean isDeeplySleeping() { ++ return getHandle().isDeeplySleeping(); ++ } ++ // Paper end ++ + @Override + public int getSleepTicks() { + return getHandle().sleepTicks; diff --git a/patches/server-unmapped/0001/0708-Fix-duplicating-give-items-on-item-drop-cancel.patch b/patches/server-unmapped/0001/0708-Fix-duplicating-give-items-on-item-drop-cancel.patch new file mode 100644 index 0000000000..cee651ba1d --- /dev/null +++ b/patches/server-unmapped/0001/0708-Fix-duplicating-give-items-on-item-drop-cancel.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alphaesia +Date: Fri, 23 Apr 2021 09:57:56 +1200 +Subject: [PATCH] Fix duplicating /give items on item drop cancel + +Fixes SPIGOT-2942 (Give command fires PlayerDropItemEvent, cancelling it causes item duplication). + +For every stack of items to give, /give puts the item stack straight +into the player's inventory. However, it also summons a "fake item" +at the player's location. When the PlayerDropItemEvent for this fake +item is cancelled, the server attempts to put the item back into the +player's inventory. The result is that the fake item, which is never +meant to be obtained, is combined with the real items injected directly +into the player's inventory. This means more items than the amount +specified in /give are given to the player - one for every stack of +items given. (e.g. /give @s dirt 1 gives you 2 dirt). + +While this isn't a big issue for general building usage, it can affect +e.g. adventure maps where the number of items the player receives is +important (and you want to restrict the player from throwing items). + +If there are any overflow items that didn't make it into the inventory +(insufficient space), those items are dropped as a real item instead +of a fake one. While cancelling this drop would also result in the +server attempting to put those items into the inventory, since it is +full this has no effect. + +Just ignoring cancellation of the PlayerDropItemEvent seems like the +cleanest and least intrusive way to fix it. + +diff --git a/src/main/java/net/minecraft/server/commands/CommandGive.java b/src/main/java/net/minecraft/server/commands/CommandGive.java +index 6685bf1757458d908e32d4069f7a8a22a28c28d7..a10207f7cb9455e29db7e6906cb2138ad5609a1f 100644 +--- a/src/main/java/net/minecraft/server/commands/CommandGive.java ++++ b/src/main/java/net/minecraft/server/commands/CommandGive.java +@@ -49,7 +49,7 @@ public class CommandGive { + + if (flag && itemstack.isEmpty()) { + itemstack.setCount(1); +- entityitem = entityplayer.drop(itemstack, false); ++ entityitem = entityplayer.drop(itemstack, false, false, true); // Paper - Fix duplicating /give items on item drop cancel + if (entityitem != null) { + entityitem.s(); + } +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index ec0956a98c133bcd3d4f92f696c667eab6ff98f1..3a62bde04d7fbb6c571cfef11d4f6891e11c7ac8 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -642,6 +642,13 @@ public abstract class EntityHuman extends EntityLiving { + + @Nullable + public EntityItem a(ItemStack itemstack, boolean flag, boolean flag1) { ++ // Paper start - Fix duplicating /give items on item drop cancel ++ return this.drop(itemstack, flag, flag1, false); ++ } ++ ++ @Nullable ++ public EntityItem drop(ItemStack itemstack, boolean flag, boolean flag1, boolean alwaysSucceed) { ++ // Paper end + if (itemstack.isEmpty()) { + return null; + } else { +@@ -683,7 +690,7 @@ public abstract class EntityHuman extends EntityLiving { + PlayerDropItemEvent event = new PlayerDropItemEvent(player, drop); + this.world.getServer().getPluginManager().callEvent(event); + +- if (event.isCancelled()) { ++ if (event.isCancelled() && !alwaysSucceed) { // Paper - Fix duplicating /give items on item drop cancel + org.bukkit.inventory.ItemStack cur = player.getInventory().getItemInHand(); + if (flag1 && (cur == null || cur.getAmount() == 0)) { + // The complete stack was dropped diff --git a/patches/server-unmapped/0001/0709-add-consumeFuel-to-FurnaceBurnEvent.patch b/patches/server-unmapped/0001/0709-add-consumeFuel-to-FurnaceBurnEvent.patch new file mode 100644 index 0000000000..410ceaf296 --- /dev/null +++ b/patches/server-unmapped/0001/0709-add-consumeFuel-to-FurnaceBurnEvent.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 22 Apr 2021 16:45:28 -0700 +Subject: [PATCH] add consumeFuel to FurnaceBurnEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +index 1997139fb87dc1947acfdf02e1f116577c3fa943..9ce19b89c16eb6edd3d5d5cc87a966a37f66895c 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityFurnace.java +@@ -331,7 +331,7 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + if (this.isBurning() && furnaceBurnEvent.isBurning()) { + // CraftBukkit end + flag1 = true; +- if (!itemstack.isEmpty()) { ++ if (!itemstack.isEmpty() && furnaceBurnEvent.willConsumeFuel()) { // Paper + Item item = itemstack.getItem(); + + itemstack.subtract(1); diff --git a/patches/server-unmapped/0001/0710-add-get-set-drop-chance-to-EntityEquipment.patch b/patches/server-unmapped/0001/0710-add-get-set-drop-chance-to-EntityEquipment.patch new file mode 100644 index 0000000000..c205b0e09c --- /dev/null +++ b/patches/server-unmapped/0001/0710-add-get-set-drop-chance-to-EntityEquipment.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 22 Apr 2021 00:28:11 -0700 +Subject: [PATCH] add get-set drop chance to EntityEquipment + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java +index 27e0e2767b11195231055f64446afb7ae5e08988..064d8adb47404b0fb839cfa646dfe04f2a2eefb6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java +@@ -245,6 +245,17 @@ public class CraftEntityEquipment implements EntityEquipment { + public void setBootsDropChance(float chance) { + setDropChance(EnumItemSlot.FEET, chance); + } ++ // Paper start ++ @Override ++ public float getDropChance(EquipmentSlot slot) { ++ return getDropChance(CraftEquipmentSlot.getNMS(slot)); ++ } ++ ++ @Override ++ public void setDropChance(EquipmentSlot slot, float chance) { ++ setDropChance(CraftEquipmentSlot.getNMS(slot), chance); ++ } ++ // Paper end + + private void setDropChance(EnumItemSlot slot, float chance) { + if (slot == EnumItemSlot.MAINHAND || slot == EnumItemSlot.OFFHAND) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +index b812e1af411dd7f4509620b6b49b7897603dc970..dacb8e127403ef5234d6bca62aa4a35431724e9e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +@@ -354,4 +354,15 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i + public void setBootsDropChance(float chance) { + throw new UnsupportedOperationException("Cannot set drop chance for PlayerInventory"); + } ++ // Paper start ++ @Override ++ public float getDropChance(EquipmentSlot slot) { ++ return 1; ++ } ++ ++ @Override ++ public void setDropChance(EquipmentSlot slot, float chance) { ++ throw new UnsupportedOperationException("Cannot set drop chance for PlayerInventory"); ++ } ++ // Paper end + } diff --git a/patches/server-unmapped/0001/0711-fix-PigZombieAngerEvent-cancellation.patch b/patches/server-unmapped/0001/0711-fix-PigZombieAngerEvent-cancellation.patch new file mode 100644 index 0000000000..532dace60c --- /dev/null +++ b/patches/server-unmapped/0001/0711-fix-PigZombieAngerEvent-cancellation.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Thu, 18 Mar 2021 21:38:01 +0100 +Subject: [PATCH] fix PigZombieAngerEvent cancellation + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java +index 59ea1432152051ce8a60c0a526db787593f0e744..1212c8e2af1f7e658d8bec7e5474a35190b1949e 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoal.java +@@ -28,6 +28,7 @@ public abstract class PathfinderGoal { + + public void c() { this.start(); } public void start() {} // Paper - OBFHELPER + ++ public final void onTaskResetObfHelper() { d(); } // Paper - OBFHELPER + public void d() { + onTaskReset(); // Paper + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityPigZombie.java b/src/main/java/net/minecraft/world/entity/monster/EntityPigZombie.java +index cc1bff409cad2eb6264d4b691599576960080ccd..d10d1b768601236b9892461ee41d61c7239d1a07 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EntityPigZombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EntityPigZombie.java +@@ -49,6 +49,7 @@ public class EntityPigZombie extends EntityZombie implements IEntityAngerable { + private UUID br; + private static final IntRange bs = TimeRange.a(4, 6); + private int bt; ++ private PathfinderGoalHurtByTarget pathfinderGoalHurtByTarget; // Paper + + public EntityPigZombie(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -69,7 +70,7 @@ public class EntityPigZombie extends EntityZombie implements IEntityAngerable { + protected void m() { + this.goalSelector.a(2, new PathfinderGoalZombieAttack(this, 1.0D, false)); + this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D)); +- this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this).a(new Class[0])); // CraftBukkit - decompile error ++ this.targetSelector.a(1, pathfinderGoalHurtByTarget = new PathfinderGoalHurtByTarget(this).a(new Class[0])); // CraftBukkit - decompile error // Paper - assign field + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, this::a_)); + this.targetSelector.a(3, new PathfinderGoalUniversalAngerReset<>(this, true)); + } +@@ -172,6 +173,7 @@ public class EntityPigZombie extends EntityZombie implements IEntityAngerable { + this.world.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + this.setAngerTarget(null); ++ pathfinderGoalHurtByTarget.onTaskResetObfHelper(); // Paper - clear goalTargets to fix cancellation + return; + } + this.setAnger(event.getNewAnger()); diff --git a/patches/server-unmapped/0001/0712-Fix-checkReach-check-for-Shulker-boxes.patch b/patches/server-unmapped/0001/0712-Fix-checkReach-check-for-Shulker-boxes.patch new file mode 100644 index 0000000000..0a4779d24e --- /dev/null +++ b/patches/server-unmapped/0001/0712-Fix-checkReach-check-for-Shulker-boxes.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 4 Apr 2021 14:25:04 -0400 +Subject: [PATCH] Fix checkReach check for Shulker boxes + + +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerShulkerBox.java b/src/main/java/net/minecraft/world/inventory/ContainerShulkerBox.java +index fdc47411aa3e0e27d3a20c18274fef0a0db9a5b1..6ef70e209a8e282f7c00d80678636c2b8aa49b7a 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerShulkerBox.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerShulkerBox.java +@@ -65,6 +65,7 @@ public class ContainerShulkerBox extends Container { + + @Override + public boolean canUse(EntityHuman entityhuman) { ++ if (!this.checkReachable) return true; // Paper - Add reachable override for ContainerShulkerBox + return this.c.a(entityhuman); + } + diff --git a/patches/server-unmapped/0001/0713-fix-PlayerItemHeldEvent-firing-twice.patch b/patches/server-unmapped/0001/0713-fix-PlayerItemHeldEvent-firing-twice.patch new file mode 100644 index 0000000000..5c4f22712a --- /dev/null +++ b/patches/server-unmapped/0001/0713-fix-PlayerItemHeldEvent-firing-twice.patch @@ -0,0 +1,30 @@ +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/network/protocol/game/PacketPlayInHeldItemSlot.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInHeldItemSlot.java +index d68f3e6b35f0af846c8a66710c5752508c095179..0e8ee44d0104ca7c666f57bdb54e0957935d5b34 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInHeldItemSlot.java ++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInHeldItemSlot.java +@@ -24,6 +24,7 @@ public class PacketPlayInHeldItemSlot implements Packet { + packetlistenerplayin.a(this); + } + ++ public int getItemInHandIndex() { return b(); } // Paper - OBFHELPER + public int b() { + return this.itemInHandIndex; + } +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 6ad075907d56a8f41ca3a7b82ff90a6d3ad9f1d4..b543776da3b799643893984a8c6f29477ed78d4a 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -1903,6 +1903,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + PlayerConnectionUtils.ensureMainThread(packetplayinhelditemslot, this, this.player.getWorldServer()); + if (this.player.isFrozen()) return; // CraftBukkit + if (packetplayinhelditemslot.b() >= 0 && packetplayinhelditemslot.b() < PlayerInventory.getHotbarSize()) { ++ if (packetplayinhelditemslot.getItemInHandIndex() == this.player.inventory.itemInHandIndex) { return; } // Paper - don't fire itemheldevent when there wasn't a slot change + PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getPlayer(), this.player.inventory.itemInHandIndex, packetplayinhelditemslot.b()); + this.server.getPluginManager().callEvent(event); + if (event.isCancelled()) { diff --git a/patches/server-unmapped/0001/0714-Added-PlayerDeepSleepEvent.patch b/patches/server-unmapped/0001/0714-Added-PlayerDeepSleepEvent.patch new file mode 100644 index 0000000000..cf56b0987c --- /dev/null +++ b/patches/server-unmapped/0001/0714-Added-PlayerDeepSleepEvent.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 21 Apr 2021 15:58:19 -0700 +Subject: [PATCH] Added PlayerDeepSleepEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 3a62bde04d7fbb6c571cfef11d4f6891e11c7ac8..3a13e7a050db7f5c93d810afe56325495cec7aa4 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -235,6 +235,11 @@ public abstract class EntityHuman extends EntityLiving { + + if (this.isSleeping()) { + ++this.sleepTicks; ++ // Paper start ++ if (this.sleepTicks == 100) { ++ if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((Player) getBukkitEntity()).callEvent()) { this.sleepTicks = Integer.MIN_VALUE; } ++ } ++ // Paper end + if (this.sleepTicks > 100) { + this.sleepTicks = 100; + } diff --git a/patches/server-unmapped/0001/0715-More-World-API.patch b/patches/server-unmapped/0001/0715-More-World-API.patch new file mode 100644 index 0000000000..88a8d987c6 --- /dev/null +++ b/patches/server-unmapped/0001/0715-More-World-API.patch @@ -0,0 +1,146 @@ +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/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index bea183ba796f2acf5465ad91e4e7fe3e73c9da74..47fbb8df04b2b77e10314666e87eaef621cffb3b 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -1870,6 +1870,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + return !this.worldDataServer.getGeneratorSettings().shouldGenerateMapFeatures() ? null : this.getChunkProvider().getChunkGenerator().findNearestMapFeature(this, structuregenerator, blockposition, i, flag); // CraftBukkit + } + ++ public BlockPosition getNearestBiome(BiomeBase biomeBase, BlockPosition blockPosition, int radius, int step) { return this.a(biomeBase, blockPosition, radius, step); } // Paper - OBFHELPER + @Nullable + public BlockPosition a(BiomeBase biomebase, BlockPosition blockposition, int i, int j) { + return this.getChunkProvider().getChunkGenerator().getWorldChunkManager().a(blockposition.getX(), blockposition.getY(), blockposition.getZ(), i, j, (biomebase1) -> { +@@ -1892,6 +1893,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + return this.savingDisabled; + } + ++ public IRegistryCustom getWorldCustomRegistry() { return r(); } // Paper - OBFHELPER + @Override + public IRegistryCustom r() { + return this.server.getCustomRegistry(); +diff --git a/src/main/java/net/minecraft/world/level/dimension/DimensionManager.java b/src/main/java/net/minecraft/world/level/dimension/DimensionManager.java +index 72bc1a1e1c2153550313e93cf7df901a514a9bef..be6d63bcf15027e02a0bfbee0792c24f2300b27e 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/DimensionManager.java ++++ b/src/main/java/net/minecraft/world/level/dimension/DimensionManager.java +@@ -142,10 +142,10 @@ public class DimensionManager { + public static IRegistryCustom.Dimension a(IRegistryCustom.Dimension iregistrycustom_dimension) { + IRegistryWritable iregistrywritable = iregistrycustom_dimension.b(IRegistry.K); + +- iregistrywritable.a(DimensionManager.OVERWORLD, (Object) DimensionManager.OVERWORLD_IMPL, Lifecycle.stable()); +- iregistrywritable.a(DimensionManager.l, (Object) DimensionManager.m, Lifecycle.stable()); +- iregistrywritable.a(DimensionManager.THE_NETHER, (Object) DimensionManager.THE_NETHER_IMPL, Lifecycle.stable()); +- iregistrywritable.a(DimensionManager.THE_END, (Object) DimensionManager.THE_END_IMPL, Lifecycle.stable()); ++ iregistrywritable.a(DimensionManager.OVERWORLD, DimensionManager.OVERWORLD_IMPL, Lifecycle.stable()); // Paper - decompile fix ++ iregistrywritable.a(DimensionManager.l, DimensionManager.m, Lifecycle.stable()); // Paper - decompile fix ++ iregistrywritable.a(DimensionManager.THE_NETHER, DimensionManager.THE_NETHER_IMPL, Lifecycle.stable()); // Paper - decompile fix ++ iregistrywritable.a(DimensionManager.THE_END, DimensionManager.THE_END_IMPL, Lifecycle.stable()); // Paper - decompile fix + return iregistrycustom_dimension; + } + +@@ -164,10 +164,10 @@ public class DimensionManager { + public static RegistryMaterials a(IRegistry iregistry, IRegistry iregistry1, IRegistry iregistry2, long i) { + RegistryMaterials registrymaterials = new RegistryMaterials<>(IRegistry.M, Lifecycle.experimental()); + +- registrymaterials.a(WorldDimension.THE_NETHER, (Object) (new WorldDimension(() -> { ++ registrymaterials.a(WorldDimension.THE_NETHER, (new WorldDimension(() -> { // Paper - decompile fix + return (DimensionManager) iregistry.d(DimensionManager.THE_NETHER); + }, b(iregistry1, iregistry2, i))), Lifecycle.stable()); +- registrymaterials.a(WorldDimension.THE_END, (Object) (new WorldDimension(() -> { ++ registrymaterials.a(WorldDimension.THE_END, (new WorldDimension(() -> { // Paper - decompile fix + return (DimensionManager) iregistry.d(DimensionManager.THE_END); + }, a(iregistry1, iregistry2, i))), Lifecycle.stable()); + return registrymaterials; +@@ -256,6 +256,7 @@ public class DimensionManager { + return this.E[i]; + } + ++ public Tag getInfiniburnTag() { return o(); } // Paper - OBFHELPER + public Tag o() { + Tag tag = TagsBlock.a().a(this.infiniburn); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 6c2a4607028c61e4a01ff200d301878e2d63b456..80de9f687d9acd7425e7c8a453c2759450869497 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2533,6 +2533,75 @@ public class CraftWorld implements World { + return (nearest == null) ? null : new Location(this, nearest.getX(), nearest.getY(), nearest.getZ()); + } + ++ // Paper start ++ @Override ++ public Location locateNearestBiome(Location origin, Biome biome, int radius) { ++ return this.locateNearestBiome(origin, biome, radius, 8); ++ } ++ ++ @Override ++ public Location locateNearestBiome(Location origin, Biome biome, int radius, int step) { ++ BlockPosition originPos = new BlockPosition(origin.getX(), origin.getY(), origin.getZ()); ++ BlockPosition nearest = getHandle().getNearestBiome(CraftBlock.biomeToBiomeBase(getHandle().getWorldCustomRegistry().b(IRegistry.ay), biome), originPos, radius, step); ++ return (nearest == null) ? null : new Location(this, nearest.getX(), nearest.getY(), nearest.getZ()); ++ } ++ ++ @Override ++ public boolean isUltrawarm() { ++ return getHandle().getDimensionManager().isNether(); ++ } ++ ++ @Override ++ public boolean isNatural() { ++ return getHandle().getDimensionManager().isNatural(); ++ } ++ ++ @Override ++ public double getCoordinateScale() { ++ return getHandle().getDimensionManager().getCoordinateScale(); ++ } ++ ++ @Override ++ public boolean hasSkylight() { ++ return getHandle().getDimensionManager().hasSkyLight(); ++ } ++ ++ @Override ++ public boolean hasBedrockCeiling() { ++ return getHandle().getDimensionManager().hasSkyLight(); ++ } ++ ++ @Override ++ public boolean isPiglinSafe() { ++ return getHandle().getDimensionManager().isPiglinSafe(); ++ } ++ ++ @Override ++ public boolean doesBedWork() { ++ return getHandle().getDimensionManager().isBedWorks(); ++ } ++ ++ @Override ++ public boolean doesRespawnAnchorWork() { ++ return getHandle().getDimensionManager().isRespawnAnchorWorks(); ++ } ++ ++ @Override ++ public boolean hasRaids() { ++ return getHandle().getDimensionManager().hasRaids(); ++ } ++ ++ @Override ++ public boolean isFixedTime() { ++ return getHandle().getDimensionManager().isFixedTime(); ++ } ++ ++ @Override ++ public Collection getInfiniburn() { ++ return com.google.common.collect.Sets.newHashSet(com.google.common.collect.Iterators.transform(getHandle().getDimensionManager().getInfiniburnTag().getTagged().iterator(), CraftMagicNumbers::getMaterial)); ++ } ++ // Paper end ++ + @Override + public Raid locateNearestRaid(Location location, int radius) { + Validate.notNull(location, "Location cannot be null"); diff --git a/patches/server-unmapped/0001/0716-Added-PlayerBedFailEnterEvent.patch b/patches/server-unmapped/0001/0716-Added-PlayerBedFailEnterEvent.patch new file mode 100644 index 0000000000..f74fd8eb88 --- /dev/null +++ b/patches/server-unmapped/0001/0716-Added-PlayerBedFailEnterEvent.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 24 Dec 2020 12:27:41 -0800 +Subject: [PATCH] Added PlayerBedFailEnterEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +index 3a13e7a050db7f5c93d810afe56325495cec7aa4..c39c50e53549e9cb9d3520bc7e8b7e89cfa20163 100644 +--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java ++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java +@@ -2233,6 +2233,7 @@ public abstract class EntityHuman extends EntityLiving { + this.g = ichatbasecomponent; + } + ++ public @Nullable IChatBaseComponent getChatComponent() { return this.a(); }; // Paper - OBFHELPER + @Nullable + public IChatBaseComponent a() { + return this.g; +diff --git a/src/main/java/net/minecraft/world/level/block/BlockBed.java b/src/main/java/net/minecraft/world/level/block/BlockBed.java +index eca84595342756e3550883551e487aaf79574fde..abe0a1c309d526de37efcac44922fa259e1d112c 100644 +--- a/src/main/java/net/minecraft/world/level/block/BlockBed.java ++++ b/src/main/java/net/minecraft/world/level/block/BlockBed.java +@@ -43,6 +43,8 @@ import net.minecraft.world.phys.shapes.VoxelShape; + import net.minecraft.world.phys.shapes.VoxelShapeCollision; + import net.minecraft.world.phys.shapes.VoxelShapes; + import org.apache.commons.lang3.ArrayUtils; ++import io.papermc.paper.event.player.PlayerBedFailEnterEvent; // Paper ++import io.papermc.paper.adventure.PaperAdventure; // Paper + + public class BlockBed extends BlockFacingHorizontal implements ITileEntity { + +@@ -101,14 +103,22 @@ public class BlockBed extends BlockFacingHorizontal implements ITileEntity { + BlockPosition finalblockposition = blockposition; + // CraftBukkit end + entityhuman.sleep(blockposition).ifLeft((entityhuman_enumbedresult) -> { ++ // Paper start - PlayerBedFailEnterEvent ++ if (entityhuman_enumbedresult != null) { ++ PlayerBedFailEnterEvent event = new PlayerBedFailEnterEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), PlayerBedFailEnterEvent.FailReason.VALUES[entityhuman_enumbedresult.ordinal()], org.bukkit.craftbukkit.block.CraftBlock.at(world, finalblockposition), entityhuman_enumbedresult == EntityHuman.EnumBedResult.NOT_POSSIBLE_HERE, PaperAdventure.asAdventure(entityhuman_enumbedresult.getChatComponent())); ++ if (!event.callEvent()) { ++ return; ++ } ++ // Paper end + // CraftBukkit start - handling bed explosion from below here +- if (entityhuman_enumbedresult == EntityHuman.EnumBedResult.NOT_POSSIBLE_HERE) { ++ if (event.getWillExplode()) { // Paper + this.explodeBed(finaliblockdata, world, finalblockposition); + } else + // CraftBukkit end + if (entityhuman_enumbedresult != null) { +- entityhuman.a(entityhuman_enumbedresult.a(), true); ++ entityhuman.a(PaperAdventure.asVanilla(event.getMessage()), true); // Paper + } ++ } // Paper + + }); + return EnumInteractionResult.SUCCESS; diff --git a/patches/server-unmapped/0002/.gitkeep b/patches/server-unmapped/0002/.gitkeep new file mode 100644 index 0000000000..e69de29bb2